Merge branch 'feature/events' into master

This commit is contained in:
Robert Beekman authored and GitHub committed 2020-11-05 21:12:56 +01:00
commit 143dea4c4c
111 files changed
+3994 -2150

No files matched your search

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cconditions_005Cwrappers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cconditional/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cdirect/@EntryIndexedValue">True</s:Boolean>
@@ -0,0 +1,19 @@
using SkiaSharp;
namespace Artemis.Core.DefaultTypes
{
internal class SKColorDesaturateModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Desaturate";
public override string Icon => "ImageMinus";
public override string Description => "Desaturates the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
// TODO: Not so straightforward ^^
currentValue.ToHsl(out float h, out float s, out float l);
s *= (parameterValue * -1 + 100f) / 100f;
return SKColor.FromHsl(h, s, l);
}
}
}
@@ -0,0 +1,18 @@
using SkiaSharp;
namespace Artemis.Core.DefaultTypes
{
internal class SKColorSaturateModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Saturate";
public override string Icon => "ImagePlus";
public override string Description => "Saturates the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
// TODO: Not so straightforward ^^
currentValue.ToHsl(out float h, out float s, out float l);
return SKColor.FromHsl(h, parameterValue, l);
}
}
}
@@ -0,0 +1,31 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Artemis.Core.JsonConverters
{
/// <summary>
/// An int converter that, if required, will round float values
/// </summary>
internal class ForgivingIntConverter : JsonConverter<int>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JValue jsonValue = serializer.Deserialize<JValue>(reader);
if (jsonValue.Type == JTokenType.Float)
return (int) Math.Round(jsonValue.Value<double>());
if (jsonValue.Type == JTokenType.Integer)
return jsonValue.Value<int>();
throw new FormatException();
}
}
}
+2 -2
View File
@@ -8,7 +8,7 @@
/// <summary>
/// Performs an update on the model
/// </summary>
/// <param name="deltaTime">The delta time in seconds</param>
void Update(double deltaTime);
/// <param name="timeline">The timeline to apply during update</param>
void Update(Timeline timeline);
}
}
@@ -7,37 +7,51 @@ namespace Artemis.Core
/// </summary>
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
{
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <inheritdoc />
public override Type RightSideType => typeof(TRightSide);
/// <summary>
/// Evaluates the operator on a and b
/// </summary>
/// <param name="a">The parameter on the left side of the expression</param>
/// <param name="b">The parameter on the right side of the expression</param>
public abstract bool Evaluate(TLeftSide a, TRightSide b);
/// <inheritdoc />
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
{
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
{
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
else
leftSide = (TLeftSide) leftSideValue;
}
else
{
leftSide = default;
}
TRightSide rightSide;
if (rightSideValue != null)
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
{
if (rightSideValue.GetType() != typeof(TRightSide) && leftSideValue is IConvertible)
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
else
rightSide = (TRightSide) rightSideValue;
}
else
{
rightSide = default;
}
return Evaluate(leftSide!, rightSide!);
}
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <inheritdoc />
public override Type RightSideType => typeof(TRightSide);
}
/// <summary>
@@ -45,6 +59,14 @@ namespace Artemis.Core
/// </summary>
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
{
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <summary>
/// Always <c>null</c>, not applicable to this type of condition operator
/// </summary>
public override Type? RightSideType => null;
/// <summary>
/// Evaluates the operator on a and b
/// </summary>
@@ -57,19 +79,18 @@ namespace Artemis.Core
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
{
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
else
leftSide = (TLeftSide) leftSideValue;
}
else
{
leftSide = default;
}
return Evaluate(leftSide!);
}
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <summary>
/// Always <c>null</c>, not applicable to this type of condition operator
/// </summary>
public override Type? RightSideType => null;
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Core
@@ -36,6 +37,8 @@ namespace Artemis.Core
_children.Insert(index.Value, dataModelConditionPart);
else
_children.Add(dataModelConditionPart);
OnChildAdded();
}
}
@@ -49,9 +52,19 @@ namespace Artemis.Core
{
dataModelConditionPart.Parent = null;
_children.Remove(dataModelConditionPart);
OnChildRemoved();
}
}
/// <summary>
/// Removes all children. You monster.
/// </summary>
public void ClearChildren()
{
while (Children.Any())
RemoveChild(Children[0]);
}
/// <summary>
/// Evaluates the condition part on the data model
/// </summary>
@@ -71,7 +84,7 @@ namespace Artemis.Core
#region IDisposable
/// <summary>
/// Disposed the condition part
/// Disposed the condition part
/// </summary>
protected virtual void Dispose(bool disposing)
{
@@ -88,5 +101,22 @@ namespace Artemis.Core
}
#endregion
#region Events
public event EventHandler ChildAdded;
public event EventHandler ChildRemoved;
protected virtual void OnChildAdded()
{
ChildAdded?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnChildRemoved()
{
ChildRemoved?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}
@@ -1,6 +1,4 @@
using System;
using System.IO;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Newtonsoft.Json;
@@ -11,20 +9,19 @@ namespace Artemis.Core
/// A predicate in a data model condition using either two data model values or one data model value and a
/// static value
/// </summary>
public class DataModelConditionPredicate : DataModelConditionPart
public abstract class DataModelConditionPredicate : DataModelConditionPart
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
/// <param name="entity">A new empty entity</param>
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
{
Parent = parent;
Entity = entity;
PredicateType = predicateType;
Entity = new DataModelConditionPredicateEntity();
Initialize();
}
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
@@ -32,8 +29,6 @@ namespace Artemis.Core
Parent = parent;
Entity = entity;
PredicateType = (ProfileRightSideType) entity.PredicateType;
Initialize();
}
/// <summary>
@@ -44,31 +39,117 @@ namespace Artemis.Core
/// <summary>
/// Gets the operator
/// </summary>
public BaseConditionOperator? Operator { get; private set; }
public BaseConditionOperator? Operator { get; protected set; }
/// <summary>
/// Gets the path of the left property
/// </summary>
public DataModelPath? LeftPath { get; private set; }
public DataModelPath? LeftPath { get; protected set; }
/// <summary>
/// Gets the path of the right property
/// </summary>
public DataModelPath? RightPath { get; private set; }
public DataModelPath? RightPath { get; protected set; }
/// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ProfileRightSideType.Static" />
/// </summary>
public object? RightStaticValue { get; private set; }
public object? RightStaticValue { get; protected set; }
internal DataModelConditionPredicateEntity Entity { get; set; }
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
}
#endregion
#region Initialization
internal void Initialize()
{
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
InitializeLeftPath();
// Operator
if (Entity.OperatorPluginGuid != null)
{
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
// Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic)
InitializeRightPath();
// Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
try
{
if (LeftPath != null && LeftPath.IsValid)
{
// Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftPath.GetPropertyType()!;
object? rightSideValue;
try
{
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
else
{
// Hope for the best...
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
}
}
catch (JsonReaderException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
}
}
protected abstract void InitializeLeftPath();
protected abstract void InitializeRightPath();
#endregion
#region Modification
/// <summary>
/// Updates the left side of the predicate
/// </summary>
/// <param name="path">The path pointing to the left side value inside the data model</param>
public void UpdateLeftSide(DataModelPath? path)
public virtual void UpdateLeftSide(DataModelPath? path)
{
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
@@ -113,6 +194,7 @@ namespace Artemis.Core
RightStaticValue = staticValue;
return;
}
// If the operator does not support a right side, always set it to null
if (Operator.RightSideType == null)
{
@@ -121,13 +203,14 @@ namespace Artemis.Core
}
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == Operator.RightSideType)
Type? preferredType = GetPreferredRightSideType();
if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null)
RightStaticValue = staticValue;
else if (staticValue != null)
RightStaticValue = Convert.ChangeType(staticValue, Operator.RightSideType);
RightStaticValue = Convert.ChangeType(staticValue, preferredType);
// If null create a default instance for value types or simply make it null for reference types
else if (Operator.RightSideType.IsValueType)
RightStaticValue = Activator.CreateInstance(Operator.RightSideType);
else if (preferredType.IsValueType)
RightStaticValue = Activator.CreateInstance(preferredType);
else
RightStaticValue = null;
}
@@ -161,137 +244,10 @@ namespace Artemis.Core
ValidateRightSide();
}
/// <inheritdoc />
public override bool Evaluate()
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = LeftPath.GetValue();
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
}
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
}
/// <inheritdoc />
internal override bool EvaluateObject(object target)
{
return false;
}
internal override void Save()
{
// Don't save an invalid state
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
return;
Entity.PredicateType = (int) PredicateType;
LeftPath?.Save();
Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
if (Operator != null)
{
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
}
internal void Initialize()
{
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side
if (Entity.LeftPath != null)
LeftPath = new DataModelPath(null, Entity.LeftPath);
// Operator
if (Entity.OperatorPluginGuid != null)
{
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
// Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
RightPath = new DataModelPath(null, Entity.RightPath);
// Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
try
{
if (LeftPath != null && LeftPath.IsValid)
{
// Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftPath.GetPropertyType()!;
object? rightSideValue;
try
{
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
else
{
// Hope for the best...
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
}
}
catch (JsonReaderException)
{
// ignored
// TODO: Some logging would be nice
}
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
/// <summary>
/// Determines the best type to use for the right side op this predicate
/// </summary>
public abstract Type? GetPreferredRightSideType();
private void ValidateOperator()
{
@@ -327,6 +283,76 @@ namespace Artemis.Core
}
}
#endregion
#region Evaluation
/// <inheritdoc />
public override bool Evaluate()
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// If the operator does not support a right side, immediately evaluate with null
if (Operator.RightSideType == null)
return Operator.InternalEvaluate(LeftPath.GetValue(), null);
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = LeftPath.GetValue();
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
}
/// <inheritdoc />
internal override bool EvaluateObject(object target)
{
return false;
}
#endregion
#region Storage
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
internal override void Save()
{
// Don't save an invalid state
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
return;
Entity.PredicateType = (int) PredicateType;
LeftPath?.Save();
Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
if (Operator != null)
{
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
}
#endregion
#region Event handlers
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
@@ -0,0 +1,241 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A condition that evaluates to true when an event is triggered
/// </summary>
public class DataModelConditionEvent : DataModelConditionPart
{
private bool _disposed;
private bool _reinitializing;
private IDataModelEvent? _event;
private bool _eventTriggered;
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
/// </summary>
/// <param name="parent"></param>
public DataModelConditionEvent(DataModelConditionPart parent)
{
Parent = parent;
Entity = new DataModelConditionEventEntity();
Initialize();
}
internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
{
Parent = parent;
Entity = entity;
Initialize();
}
/// <summary>
/// Gets the path of the event property
/// </summary>
public DataModelPath? EventPath { get; private set; }
public Type? EventArgumentType { get; set; }
internal DataModelConditionEventEntity Entity { get; set; }
/// <inheritdoc />
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionEvent");
// Ensure the event has not been replaced
if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent)
SubscribeToDataModelEvent(dataModelEvent);
// Only evaluate to true once every time the event has been triggered
if (!_eventTriggered)
return false;
_eventTriggered = false;
// If there is a child (root group), it must evaluate to true whenever the event triggered
if (Children.Any())
return Children[0].EvaluateObject(_event.LastEventArgumentsUntyped);
// If there are no children, we always evaluate to true whenever the event triggered
return true;
}
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
{
if (_event != null)
_event.EventTriggered -= OnEventTriggered;
_event = dataModelEvent;
if (_event != null)
_event.EventTriggered += OnEventTriggered;
}
/// <summary>
/// Updates the event the condition is triggered by
/// </summary>
public void UpdateEvent(DataModelPath? path)
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionEvent");
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update event to an invalid path");
EventPath?.Dispose();
EventPath = path != null ? new DataModelPath(path) : null;
SubscribeToEventPath();
// Remove the old root group that was tied to the old data model
ClearChildren();
if (EventPath != null)
{
EventArgumentType = GetEventArgumentType();
// Create a new root group
AddChild(new DataModelConditionGroup(this));
}
else
{
EventArgumentType = null;
}
}
private Type? GetEventArgumentType()
{
if (EventPath == null || !EventPath.IsValid)
return null;
// Cannot rely on EventPath.GetValue() because part of the path might be null
Type eventType = EventPath.GetPropertyType()!;
return eventType.IsGenericType ? eventType.GetGenericArguments()[0] : typeof(DataModelEventArgs);
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
EventPath?.Dispose();
foreach (DataModelConditionPart child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object target)
{
return false;
}
internal override void Save()
{
// Don't save an invalid state
if (EventPath != null && !EventPath.IsValid)
return;
// Target list
EventPath?.Save();
Entity.EventPath = EventPath?.Entity;
// Children
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (DataModelConditionPart child in Children)
child.Save();
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
internal void Initialize()
{
ClearChildren();
if (Entity.EventPath == null)
return;
// Ensure the list path is valid and points to a list
DataModelPath eventPath = new DataModelPath(null, Entity.EventPath);
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
if (eventPath.IsValid && !PointsToEvent(eventPath))
return;
EventPath = eventPath;
SubscribeToEventPath();
EventArgumentType = GetEventArgumentType();
// There should only be one child and it should be a group
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
{
AddChild(new DataModelConditionGroup(this, rootGroup));
}
else
{
Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this));
}
}
private bool PointsToEvent(DataModelPath dataModelPath)
{
Type? type = dataModelPath.GetPropertyType();
if (type == null)
return false;
return typeof(IDataModelEvent).IsAssignableFrom(type);
}
private void SubscribeToEventPath()
{
if (EventPath == null) return;
EventPath.PathValidated += EventPathOnPathValidated;
EventPath.PathInvalidated += EventPathOnPathInvalidated;
}
#region Event handlers
private void OnEventTriggered(object? sender, EventArgs e)
{
_eventTriggered = true;
}
private void EventPathOnPathValidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
EventPath?.Dispose();
Initialize();
_reinitializing = false;
}
private void EventPathOnPathInvalidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
EventPath?.Dispose();
Initialize();
_reinitializing = false;
}
#endregion
}
}
@@ -0,0 +1,161 @@
using System;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A predicate like evaluated inside a <see cref="DataModelConditionEvent" />
/// </summary>
public class DataModelConditionEventPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEventPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionEventPredicateEntity())
{
DataModelConditionEvent = null!;
ApplyParentEvent();
Initialize();
}
internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity)
: base(parent, entity)
{
DataModelConditionEvent = null!;
ApplyParentEvent();
Initialize();
}
/// <summary>
/// Gets the data model condition event this predicate belongs to
/// </summary>
public DataModelConditionEvent DataModelConditionEvent { get; private set; }
private void ApplyParentEvent()
{
DataModelConditionPart? current = Parent;
while (current != null)
{
if (current is DataModelConditionEvent parentEvent)
{
DataModelConditionEvent = parentEvent;
return;
}
current = current.Parent;
}
if (DataModelConditionEvent == null)
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event");
}
private object? GetEventPathValue(DataModelPath path, object target)
{
lock (path)
{
if (!(path.Target is EventPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target");
wrapper.UntypedArguments = target;
return path.GetValue();
}
}
#region Initialization
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = DataModelConditionEvent.EventArgumentType != null
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
: null;
}
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{
// Right side dynamic using event arguments
if (Entity.RightPath.WrapperType == PathWrapperType.Event)
{
RightPath = DataModelConditionEvent.EventArgumentType != null
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath)
: null;
}
// Right side dynamic
else
RightPath = new DataModelPath(null, Entity.RightPath);
}
}
#endregion
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Evaluation
/// <summary>
/// Not supported for event predicates, always returns <c>false</c>
/// </summary>
public override bool Evaluate()
{
return false;
}
internal override bool EvaluateObject(object target)
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// If the operator does not support a right side, immediately evaluate with null
if (Operator.RightSideType == null)
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null);
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = GetEventPathValue(LeftPath, target);
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
if (PredicateType == ProfileRightSideType.Dynamic)
{
// If the path targets a property inside the event, evaluate on the event path value instead of the right path value
if (RightPath.Target is EventPredicateWrapperDataModel)
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target));
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue());
}
return false;
}
#endregion
}
}
@@ -0,0 +1,63 @@
using System;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A predicate in a data model condition using either two data model values or one data model value and a
/// static value
/// </summary>
public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionGeneralPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
{
Initialize();
}
internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
: base(parent, entity)
{
Initialize();
}
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Initialization
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = new DataModelPath(null, Entity.LeftPath);
}
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
RightPath = new DataModelPath(null, Entity.RightPath);
}
#endregion
}
}
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@@ -21,6 +23,8 @@ namespace Artemis.Core
{
Parent = parent;
Entity = new DataModelConditionGroupEntity();
ChildAdded += OnChildrenChanged;
ChildRemoved += OnChildrenChanged;
}
/// <summary>
@@ -40,11 +44,19 @@ namespace Artemis.Core
AddChild(new DataModelConditionGroup(this, groupEntity));
else if (childEntity is DataModelConditionListEntity listEntity)
AddChild(new DataModelConditionList(this, listEntity));
else if (childEntity is DataModelConditionPredicateEntity predicateEntity)
AddChild(new DataModelConditionPredicate(this, predicateEntity));
else if (childEntity is DataModelConditionEventEntity eventEntity)
AddChild(new DataModelConditionEvent(this, eventEntity));
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
}
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
ChildAdded += OnChildrenChanged;
ChildRemoved += OnChildrenChanged;
}
/// <summary>
@@ -52,6 +64,11 @@ namespace Artemis.Core
/// </summary>
public BooleanOperator BooleanOperator { get; set; }
/// <summary>
/// Gets whether this group contains any events
/// </summary>
public bool ContainsEvents { get; private set; }
internal DataModelConditionGroupEntity Entity { get; set; }
/// <inheritdoc />
@@ -67,16 +84,26 @@ namespace Artemis.Core
if (Children.Count == 1)
return Children[0].Evaluate();
if (ContainsEvents)
{
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
}
return EvaluateWithOperator(Children);
}
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
{
switch (BooleanOperator)
{
case BooleanOperator.And:
return Children.All(c => c.Evaluate());
return targets.All(c => c.Evaluate());
case BooleanOperator.Or:
return Children.Any(c => c.Evaluate());
return targets.Any(c => c.Evaluate());
case BooleanOperator.AndNot:
return Children.All(c => !c.Evaluate());
return targets.All(c => !c.Evaluate());
case BooleanOperator.OrNot:
return Children.Any(c => !c.Evaluate());
return targets.Any(c => !c.Evaluate());
default:
throw new ArgumentOutOfRangeException();
}
@@ -133,6 +160,11 @@ namespace Artemis.Core
{
return Entity;
}
private void OnChildrenChanged(object? sender, EventArgs e)
{
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
}
}
/// <summary>
@@ -180,7 +180,7 @@ namespace Artemis.Core
DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
Type listType = listPath.GetPropertyType()!;
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
if (listPath.IsValid && !listPath.PointsToList)
if (listPath.IsValid && !PointsToList(listPath))
return;
ListPath = listPath;
@@ -208,6 +208,12 @@ namespace Artemis.Core
}
}
private bool PointsToList(DataModelPath dataModelPath)
{
Type? type = dataModelPath.GetPropertyType();
return type?.IsGenericEnumerable() ?? false;
}
private void SubscribeToListPath()
{
if (ListPath == null) return;
@@ -1,16 +1,13 @@
using System;
using System.Reflection;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Conditions;
using Newtonsoft.Json;
namespace Artemis.Core
{
/// <summary>
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
/// </summary>
public class DataModelConditionListPredicate : DataModelConditionPart
public class DataModelConditionListPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
@@ -18,138 +15,104 @@ namespace Artemis.Core
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
{
Parent = parent;
PredicateType = predicateType;
Entity = new DataModelConditionListPredicateEntity();
DataModelConditionList = null!;
ApplyParentList();
Initialize();
}
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
: base(parent, entity)
{
Parent = parent;
Entity = entity;
PredicateType = (ProfileRightSideType) entity.PredicateType;
DataModelConditionList = null!;
ApplyParentList();
Initialize();
}
/// <summary>
/// Gets or sets the predicate type
/// </summary>
public ProfileRightSideType PredicateType { get; set; }
/// <summary>
/// Gets the operator
/// </summary>
public BaseConditionOperator? Operator { get; private set; }
/// <summary>
/// Gets the data model condition list this predicate belongs to
/// </summary>
public DataModelConditionList DataModelConditionList { get; private set; }
/// <summary>
/// Gets the path of the left property
/// </summary>
public DataModelPath? LeftPath { get; private set; }
/// <summary>
/// Gets the path of the right property
/// </summary>
public DataModelPath? RightPath { get; private set; }
/// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ProfileRightSideType.Static" />
/// </summary>
public object? RightStaticValue { get; private set; }
internal DataModelConditionListPredicateEntity Entity { get; set; }
/// <summary>
/// Updates the left side of the predicate
/// </summary>
/// <param name="path">The path pointing to the left side value inside the list</param>
public void UpdateLeftSide(DataModelPath? path)
private void ApplyParentList()
{
if (DataModelConditionList.IsPrimitiveList)
throw new ArtemisCoreException("Cannot apply a left side to a predicate inside a primitive list");
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
LeftPath?.Dispose();
LeftPath = path != null ? new DataModelPath(path) : null;
ValidateOperator();
ValidateRightSide();
}
/// <summary>
/// Updates the right side of the predicate using a path and makes the predicate dynamic
/// </summary>
/// <param name="path">The path pointing to the right side value</param>
public void UpdateRightSideDynamic(DataModelPath? path)
{
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
RightPath?.Dispose();
RightPath = path != null ? new DataModelPath(path) : null;
PredicateType = ProfileRightSideType.Dynamic;
}
/// <summary>
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
/// </summary>
/// <param name="staticValue">The right side value to use</param>
public void UpdateRightSideStatic(object? staticValue)
{
PredicateType = ProfileRightSideType.Static;
RightPath?.Dispose();
RightPath = null;
SetStaticValue(staticValue);
}
/// <summary>
/// Updates the operator of the predicate and re-compiles the expression
/// </summary>
/// <param name="conditionOperator"></param>
public void UpdateOperator(BaseConditionOperator? conditionOperator)
{
if (conditionOperator == null)
DataModelConditionPart? current = Parent;
while (current != null)
{
Operator = null;
return;
if (current is DataModelConditionList parentList)
{
DataModelConditionList = parentList;
return;
}
current = current.Parent;
}
// No need to clear compiled expressions, without a left data model they are already null
if (LeftPath == null || !LeftPath.IsValid)
{
Operator = conditionOperator;
return;
}
Type leftType = LeftPath.GetPropertyType()!;
// Left side can't go empty so enforce a match
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}");
Operator = conditionOperator;
ValidateRightSide();
if (DataModelConditionList == null)
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
}
private object? GetListPathValue(DataModelPath path, object target)
{
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
wrapper.UntypedValue = target;
return path.GetValue();
}
#region Initialization
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath)
: null;
}
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{
// Right side dynamic inside the list
if (Entity.RightPath.WrapperType == PathWrapperType.List)
{
RightPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath)
: null;
}
// Right side dynamic
else
RightPath = new DataModelPath(null, Entity.RightPath);
}
}
#endregion
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = DataModelConditionList.IsPrimitiveList
? DataModelConditionList.ListType
: LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Evaluation
/// <summary>
/// Not supported for list predicates, always returns <c>false</c>
/// </summary>
@@ -158,45 +121,6 @@ namespace Artemis.Core
return false;
}
/// <summary>
/// Determines whether the provided path is contained inside the list
/// </summary>
/// <param name="path">The path to evaluate</param>
public bool ListContainsInnerPath(string path)
{
if (DataModelConditionList.ListType == null)
return false;
string[] parts = path.Split('.');
Type current = DataModelConditionList.ListType;
foreach (string part in parts)
{
PropertyInfo? property = current.GetProperty(part);
current = property?.PropertyType;
if (property == null)
return false;
}
return true;
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object target)
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
@@ -227,226 +151,6 @@ namespace Artemis.Core
return false;
}
internal Type GetTypeAtInnerPath(string path)
{
if (!ListContainsInnerPath(path))
return null;
string[] parts = path.Split('.');
Type current = DataModelConditionList.ListType;
Type result = null;
foreach (string part in parts)
{
PropertyInfo? property = current.GetProperty(part);
current = property.PropertyType;
result = property.PropertyType;
}
return result;
}
internal override void Save()
{
Entity.PredicateType = (int) PredicateType;
LeftPath?.Save();
Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
if (Operator != null)
{
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
private void ApplyParentList()
{
DataModelConditionPart? current = Parent;
while (current != null)
{
if (current is DataModelConditionList parentList)
{
DataModelConditionList = parentList;
return;
}
current = current.Parent;
}
if (DataModelConditionList == null)
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
}
private void Initialize()
{
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side
if (Entity.LeftPath != null)
{
LeftPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath)
: null;
}
// Operator
if (Entity.OperatorPluginGuid != null)
{
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
// Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{
// Right side dynamic inside the list
// TODO: Come up with a general wrapper solution because this will clash with events
if (Entity.RightPath.DataModelGuid == Constants.CorePluginInfo.Guid)
{
RightPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath)
: null;
}
// Right side dynamic
else
RightPath = new DataModelPath(null, Entity.RightPath);
}
// Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
try
{
if (LeftPath != null && LeftPath.IsValid)
{
// Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftPath.GetPropertyType()!;
object? rightSideValue;
try
{
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogListPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
else
{
// Hope for the best... we must infer the type from JSON now
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
}
}
catch (JsonException e)
{
DeserializationLogger.LogListPredicateDeserializationFailure(this, e);
}
}
private void ValidateOperator()
{
if (LeftPath == null || !LeftPath.IsValid || Operator == null)
return;
Type leftType = LeftPath.GetPropertyType()!;
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
Operator = null;
}
private void ValidateRightSide()
{
if (Operator == null)
return;
if (PredicateType == ProfileRightSideType.Dynamic)
{
if (RightPath == null || !RightPath.IsValid)
return;
Type rightSideType = RightPath.GetPropertyType()!;
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
UpdateRightSideDynamic(null);
}
else
{
if (RightStaticValue == null)
return;
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
UpdateRightSideStatic(null);
}
}
private void SetStaticValue(object? staticValue)
{
RightPath?.Dispose();
RightPath = null;
// If the operator is null simply apply the value, any validation will wait
if (Operator == null)
{
RightStaticValue = staticValue;
return;
}
// If the operator does not support a right side, always set it to null
if (Operator.RightSideType == null)
{
RightStaticValue = null;
return;
}
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == Operator.RightSideType)
RightStaticValue = staticValue;
else if (staticValue != null)
RightStaticValue = Convert.ChangeType(staticValue, Operator.RightSideType);
// If null create a default instance for value types or simply make it null for reference types
else if (Operator.RightSideType.IsValueType)
RightStaticValue = Activator.CreateInstance(Operator.RightSideType);
else
RightStaticValue = null;
}
private object? GetListPathValue(DataModelPath path, object target)
{
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
wrapper.UntypedValue = target;
return path.GetValue();
}
#region Event handlers
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator);
}
private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e)
{
if (e.Registration.ConditionOperator != Operator)
return;
Operator = null;
}
#endregion
}
}
@@ -0,0 +1,37 @@
using System;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
internal class EventPredicateWrapperDataModel<T> : EventPredicateWrapperDataModel
{
[DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")]
public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!;
}
/// <summary>
/// Represents a datamodel that wraps the event arguments of an event
/// </summary>
public abstract class EventPredicateWrapperDataModel : DataModel
{
internal EventPredicateWrapperDataModel()
{
PluginInfo = Constants.CorePluginInfo;
}
[DataModelIgnore]
public object? UntypedArguments { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="EventPredicateWrapperDataModel"/> class
/// </summary>
public static EventPredicateWrapperDataModel Create(Type type)
{
object? instance = Activator.CreateInstance(typeof(EventPredicateWrapperDataModel<>).MakeGenericType(type));
if (instance == null)
throw new ArtemisCoreException($"Failed to create an instance of EventPredicateWrapperDataModel<T> for type {type.Name}");
return (EventPredicateWrapperDataModel) instance;
}
}
}
@@ -9,6 +9,9 @@ namespace Artemis.Core
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
}
/// <summary>
/// Represents a datamodel that wraps a value in a list
/// </summary>
public abstract class ListPredicateWrapperDataModel : DataModel
{
internal ListPredicateWrapperDataModel()
@@ -19,6 +22,9 @@ namespace Artemis.Core
[DataModelIgnore]
public object? UntypedValue { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel"/> class
/// </summary>
public static ListPredicateWrapperDataModel Create(Type type)
{
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
@@ -60,9 +60,9 @@ namespace Artemis.Core
/// Gets ors ets the easing function of the data binding
/// </summary>
public Easings.Functions EasingFunction { get; set; }
internal DataBindingEntity Entity { get; }
/// <summary>
/// Gets the current value of the data binding
/// </summary>
@@ -137,16 +137,26 @@ namespace Artemis.Core
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
public void Update(double deltaTime)
/// <param name="timeline">The timeline to apply during update</param>
public void Update(Timeline timeline)
{
UpdateWithDelta(timeline.Delta);
}
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="delta">The delta to apply during update</param>
public void UpdateWithDelta(TimeSpan delta)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime);
if (delta < TimeSpan.Zero)
delta = TimeSpan.Zero;
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime));
_easingProgress = _easingProgress.Add(delta);
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
}
@@ -75,7 +75,7 @@ namespace Artemis.Core
return condition;
}
/// <summary>
/// Removes a condition from the conditional data binding's <see cref="Conditions" /> collection and disposes it
/// </summary>
@@ -200,7 +200,7 @@ namespace Artemis.Core
ParameterPath = new DataModelPath(null, Entity.ParameterPath);
}
// Static parameter
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null)
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null)
{
// Use the target type so JSON.NET has a better idea what to do
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
@@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
/// <summary>
/// Represents a data model event with event arguments of type <typeparamref name="T" />
/// </summary>
public class DataModelEvent<T> : IDataModelEvent where T : DataModelEventArgs
{
private bool _trackHistory;
/// <summary>
/// Creates a new instance of the <see cref="DataModelEvent{T}" /> class with history tracking disabled
/// </summary>
public DataModelEvent()
{
}
/// <summary>
/// Creates a new instance of the <see cref="DataModelEvent{T}" />
/// </summary>
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
public DataModelEvent(bool trackHistory)
{
_trackHistory = trackHistory;
}
/// <inheritdoc />
[DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
public DateTime LastTrigger { get; private set; }
/// <summary>
/// Gets the event arguments of the last time the event was triggered
/// </summary>
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
public T? LastEventArguments { get; private set; }
/// <inheritdoc />
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
public int TriggerCount { get; private set; }
/// <summary>
/// Gets a queue of the last 20 event arguments
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
/// </summary>
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
public Queue<T> EventArgumentsHistory { get; } = new Queue<T>(20);
/// <summary>
/// Trigger the event with the given <paramref name="eventArgs" />
/// </summary>
/// <param name="eventArgs">The event argument to pass to the event</param>
public void Trigger(T eventArgs)
{
if (eventArgs == null) throw new ArgumentNullException(nameof(eventArgs));
eventArgs.TriggerTime = DateTime.Now;
LastEventArguments = eventArgs;
LastTrigger = DateTime.Now;
TriggerCount++;
if (TrackHistory)
{
lock (EventArgumentsHistory)
{
if (EventArgumentsHistory.Count == 20)
EventArgumentsHistory.Dequeue();
EventArgumentsHistory.Enqueue(eventArgs);
}
}
OnEventTriggered();
}
internal virtual void OnEventTriggered()
{
EventTriggered?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
[DataModelIgnore]
public Type ArgumentsType => typeof(T);
/// <inheritdoc />
[DataModelIgnore]
public bool TrackHistory
{
get => _trackHistory;
set
{
EventArgumentsHistory.Clear();
_trackHistory = value;
}
}
/// <inheritdoc />
[DataModelIgnore]
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
/// <inheritdoc />
[DataModelIgnore]
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.Cast<DataModelEventArgs>().ToList();
/// <inheritdoc />
public event EventHandler? EventTriggered;
/// <inheritdoc />
public void Reset()
{
TriggerCount = 0;
EventArgumentsHistory.Clear();
}
}
/// <summary>
/// Represents a data model event without event arguments
/// </summary>
public class DataModelEvent : IDataModelEvent
{
private bool _trackHistory;
/// <summary>
/// Creates a new instance of the <see cref="DataModelEvent" /> class with history tracking disabled
/// </summary>
public DataModelEvent()
{
}
/// <summary>
/// Creates a new instance of the <see cref="DataModelEvent" />
/// </summary>
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
public DataModelEvent(bool trackHistory)
{
_trackHistory = trackHistory;
}
/// <inheritdoc />
[DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
public DateTime LastTrigger { get; private set; }
/// <summary>
/// Gets the event arguments of the last time the event was triggered
/// </summary>
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
public DataModelEventArgs? LastEventArguments { get; private set; }
/// <inheritdoc />
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
public int TriggerCount { get; private set; }
/// <summary>
/// Gets a queue of the last 20 event arguments
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
/// </summary>
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
public Queue<DataModelEventArgs> EventArgumentsHistory { get; } = new Queue<DataModelEventArgs>(20);
/// <summary>
/// Trigger the event
/// </summary>
public void Trigger()
{
DataModelEventArgs eventArgs = new DataModelEventArgs {TriggerTime = DateTime.Now};
LastEventArguments = eventArgs;
LastTrigger = DateTime.Now;
TriggerCount++;
if (TrackHistory)
{
lock (EventArgumentsHistory)
{
if (EventArgumentsHistory.Count == 20)
EventArgumentsHistory.Dequeue();
EventArgumentsHistory.Enqueue(eventArgs);
}
}
OnEventTriggered();
}
internal virtual void OnEventTriggered()
{
EventTriggered?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
[DataModelIgnore]
public Type ArgumentsType => typeof(DataModelEventArgs);
/// <inheritdoc />
[DataModelIgnore]
public bool TrackHistory
{
get => _trackHistory;
set
{
EventArgumentsHistory.Clear();
_trackHistory = value;
}
}
/// <inheritdoc />
[DataModelIgnore]
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
/// <inheritdoc />
[DataModelIgnore]
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.ToList();
/// <inheritdoc />
public event EventHandler? EventTriggered;
/// <inheritdoc />
public void Reset()
{
TriggerCount = 0;
EventArgumentsHistory.Clear();
}
}
}
@@ -0,0 +1,15 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents the base class for data model events that contain event data
/// </summary>
public class DataModelEventArgs
{
/// <summary>
/// Gets the time at which the event with these arguments was triggered
/// </summary>
public DateTime TriggerTime { get; internal set; }
}
}
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@@ -111,18 +110,6 @@ namespace Artemis.Core
/// </summary>
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
/// <summary>
/// Gets a boolean indicating whether this data model path points to a list
/// </summary>
public bool PointsToList
{
get
{
Type? type = GetPropertyType();
return type?.IsGenericEnumerable() ?? false;
}
}
internal DataModelPathEntity Entity { get; }
internal Func<object, object>? Accessor { get; private set; }
@@ -293,6 +280,13 @@ namespace Artemis.Core
Entity.Path = Path;
Entity.DataModelGuid = DataModelGuid;
Entity.WrapperType = Target switch
{
ListPredicateWrapperDataModel _ => PathWrapperType.List,
EventPredicateWrapperDataModel _ => PathWrapperType.Event,
_ => PathWrapperType.None
};
}
#endregion
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core
{
internal interface IDataModelEvent
{
/// <summary>
/// Gets the last time the event was triggered
/// </summary>
DateTime LastTrigger { get; }
/// <summary>
/// Gets the amount of times the event was triggered
/// </summary>
int TriggerCount { get; }
/// <summary>
/// Gets the type of arguments this event contains
/// </summary>
Type ArgumentsType { get; }
/// <summary>
/// Gets or sets a boolean indicating whether the last 20 events should be tracked
/// <para>Note: setting this to <see langword="false" /> will clear the current history</para>
/// </summary>
bool TrackHistory { get; set; }
/// <summary>
/// Gets the event arguments of the last time the event was triggered by its base type
/// </summary>
public DataModelEventArgs? LastEventArgumentsUntyped { get; }
/// <summary>
/// Gets a list of the last 20 event arguments by their base type.
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
/// </summary>
public List<DataModelEventArgs> EventArgumentsHistoryUntyped { get; }
/// <summary>
/// Fires when the event is triggered
/// </summary>
event EventHandler EventTriggered;
/// <summary>
/// Resets the trigger count and history of this data model event
/// </summary>
void Reset();
}
}
+144 -172
View File
@@ -30,19 +30,16 @@ namespace Artemis.Core
Profile = Parent.Profile;
Name = name;
Enabled = true;
DisplayContinuously = true;
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
ApplyRenderElementDefaults();
Parent.AddChild(this);
}
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
{
FolderEntity = folderEntity;
EntityId = folderEntity.Id;
Profile = profile;
@@ -53,125 +50,195 @@ namespace Artemis.Core
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
Load();
}
/// <summary>
/// Gets a boolean indicating whether this folder is at the root of the profile tree
/// </summary>
public bool IsRootFolder => Parent == Profile;
/// <summary>
/// Gets the longest timeline of all this folders children
/// </summary>
public Timeline LongestChildTimeline { get; private set; }
internal FolderEntity FolderEntity { get; set; }
internal override RenderElementEntity RenderElementEntity => FolderEntity;
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
List<ILayerProperty> result = new List<ILayerProperty>();
foreach (BaseLayerEffect layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result;
}
internal override RenderElementEntity RenderElementEntity => FolderEntity;
public bool IsRootFolder => Parent == Profile;
public override void Update(double deltaTime)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Folder");
if (!Enabled)
return;
// Disable data bindings during an override
bool wasApplyingDataBindings = ApplyDataBindingsEnabled;
ApplyDataBindingsEnabled = false;
UpdateDisplayCondition();
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
// to it's start
UpdateTimeline(deltaTime);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(deltaTime);
baseLayerEffect.Update(deltaTime);
}
// Iterate the children in reverse because that's how they must be rendered too
for (int index = Children.Count - 1; index > -1; index--)
{
ProfileElement profileElement = Children[index];
profileElement.Update(deltaTime);
}
// Restore the old data bindings enabled state
ApplyDataBindingsEnabled = wasApplyingDataBindings;
foreach (ProfileElement child in Children)
child.Update(deltaTime);
}
protected internal override void UpdateTimelineLength()
/// <inheritdoc />
public override void Reset()
{
TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType<RenderProfileElement>().Max(c => c.TimelineLength);
if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength)
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
DisplayConditionMet = false;
Timeline.JumpToStart();
if (Parent is RenderProfileElement parent)
parent.UpdateTimelineLength();
foreach (ProfileElement child in Children)
child.Reset();
}
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
/// <inheritdoc />
public override void AddChild(ProfileElement child, int? order = null)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Folder");
if (!Enabled)
base.AddChild(child, order);
CalculateRenderProperties();
}
/// <inheritdoc />
public override void RemoveChild(ProfileElement child)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
base.RemoveChild(child);
CalculateRenderProperties();
}
public override string ToString()
{
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
public void CalculateRenderProperties()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
foreach (ProfileElement child in Children)
if (child is RenderProfileElement effectChild && effectChild.Path != null)
path.AddPath(effectChild.Path);
Path = path;
// Folder render properties are based on child paths and thus require an update
if (Parent is Folder folder)
folder.CalculateRenderProperties();
OnRenderPropertiesUpdated();
}
protected override void Dispose(bool disposing)
{
Disposed = true;
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
_folderBitmap?.Dispose();
base.Dispose(disposing);
}
internal override void Load()
{
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
// Load child folders
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId;
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
FolderEntity.Order = Order;
FolderEntity.Name = Name;
FolderEntity.Enabled = Enabled;
FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
SaveRenderElement();
}
#region Rendering
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
if (!Enabled || !Children.Any(c => c.Enabled))
return;
TimeSpan beginTime = TimelinePosition;
// Ensure the folder is ready
if (Path == null)
return;
if (stickToMainSegment)
// No point rendering if none of the children are going to render
if (!Children.Any(c => c is RenderProfileElement renderElement && !renderElement.Timeline.IsFinished))
return;
lock (Timeline)
{
if (!DisplayContinuously)
{
TimeSpan position = timeOverride + StartSegmentLength;
if (position > StartSegmentLength + EndSegmentLength)
TimelinePosition = StartSegmentLength + EndSegmentLength;
}
else
{
double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds;
if (progress > 0)
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
else
TimelinePosition = StartSegmentLength;
}
}
else
TimelinePosition = timeOverride;
double delta = (TimelinePosition - beginTime).TotalSeconds;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(delta);
baseLayerEffect.Update(delta);
RenderFolder(Timeline, canvas, canvasInfo);
Timeline.ClearDelta();
}
}
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
private void PrepareForRender(Timeline timeline)
{
if (_disposed)
throw new ObjectDisposedException("Folder");
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(timeline);
baseLayerEffect.Update(timeline.Delta.TotalSeconds);
}
}
if (Path == null || !Enabled || !Children.Any(c => c.Enabled))
return;
// No need to render if at the end of the timeline
if (TimelinePosition > TimelineLength)
return;
private void RenderFolder(Timeline timeline, SKCanvas canvas, SKImageInfo canvasInfo)
{
PrepareForRender(timeline);
if (_folderBitmap == null)
{
_folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
}
else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height)
{
_folderBitmap.Dispose();
@@ -207,7 +274,7 @@ namespace Artemis.Core
{
folderCanvas.Save();
ProfileElement profileElement = Children[index];
profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info);
profileElement.Render(folderCanvas, _folderBitmap.Info);
folderCanvas.Restore();
}
@@ -225,102 +292,7 @@ namespace Artemis.Core
canvas.Restore();
}
/// <inheritdoc />
public override void AddChild(ProfileElement child, int? order = null)
{
if (_disposed)
throw new ObjectDisposedException("Folder");
base.AddChild(child, order);
UpdateTimelineLength();
CalculateRenderProperties();
}
/// <inheritdoc />
public override void RemoveChild(ProfileElement child)
{
if (_disposed)
throw new ObjectDisposedException("Folder");
base.RemoveChild(child);
UpdateTimelineLength();
CalculateRenderProperties();
}
public override string ToString()
{
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
public void CalculateRenderProperties()
{
if (_disposed)
throw new ObjectDisposedException("Folder");
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
foreach (ProfileElement child in Children)
{
if (child is RenderProfileElement effectChild && effectChild.Path != null)
path.AddPath(effectChild.Path);
}
Path = path;
// Folder render properties are based on child paths and thus require an update
if (Parent is Folder folder)
folder.CalculateRenderProperties();
OnRenderPropertiesUpdated();
}
protected override void Dispose(bool disposing)
{
_disposed = true;
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
_folderBitmap?.Dispose();
base.Dispose(disposing);
}
internal override void Load()
{
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
// Load child folders
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (_disposed)
throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId;
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
FolderEntity.Order = Order;
FolderEntity.Name = Name;
FolderEntity.Enabled = Enabled;
FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
SaveRenderElement();
}
#endregion
#region Events
+124 -120
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
@@ -38,7 +37,6 @@ namespace Artemis.Core
Profile = Parent.Profile;
Name = name;
Enabled = true;
DisplayContinuously = true;
General = new LayerGeneralProperties();
Transform = new LayerTransformProperties();
@@ -47,14 +45,14 @@ namespace Artemis.Core
_expandedPropertyGroups = new List<string>();
Initialize();
ApplyRenderElementDefaults();
Parent.AddChild(this);
}
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
{
LayerEntity = layerEntity;
EntityId = layerEntity.Id;
Profile = profile;
Parent = parent;
General = new LayerGeneralProperties();
@@ -68,27 +66,6 @@ namespace Artemis.Core
Initialize();
}
internal LayerEntity LayerEntity { get; set; }
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
List<ILayerProperty> result = new List<ILayerProperty>();
result.AddRange(General.GetAllLayerProperties());
result.AddRange(Transform.GetAllLayerProperties());
if (LayerBrush?.BaseProperties != null)
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
foreach (BaseLayerEffect layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result;
}
internal override RenderElementEntity RenderElementEntity => LayerEntity;
/// <summary>
/// A collection of all the LEDs this layer is assigned to.
/// </summary>
@@ -131,6 +108,25 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _layerBrush, value);
}
internal LayerEntity LayerEntity { get; set; }
internal override RenderElementEntity RenderElementEntity => LayerEntity;
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
List<ILayerProperty> result = new List<ILayerProperty>();
result.AddRange(General.GetAllLayerProperties());
result.AddRange(Transform.GetAllLayerProperties());
if (LayerBrush?.BaseProperties != null)
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
foreach (BaseLayerEffect layerEffect in LayerEffects)
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
return result;
}
/// <inheritdoc />
public override string ToString()
{
@@ -142,7 +138,7 @@ namespace Artemis.Core
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
Disposed = true;
// Brush first in case it depends on any of the other disposables during it's own disposal
_layerBrush?.Dispose();
@@ -195,7 +191,7 @@ namespace Artemis.Core
internal override void Save()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
// Properties
@@ -258,94 +254,30 @@ namespace Artemis.Core
/// <inheritdoc />
public override void Update(double deltaTime)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
if (!Enabled)
return;
// Ensure the layer must still be displayed
UpdateDisplayCondition();
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
// to it's start
UpdateTimeline(deltaTime);
// No point updating further than this if the layer is not going to be rendered
if (TimelinePosition > TimelineLength)
return;
General.Update(deltaTime);
Transform.Update(deltaTime);
LayerBrush.BaseProperties?.Update(deltaTime);
LayerBrush.Update(deltaTime);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(deltaTime);
baseLayerEffect.Update(deltaTime);
}
}
protected internal override void UpdateTimelineLength()
{
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
}
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
{
if (_disposed)
throw new ObjectDisposedException("Layer");
if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
return;
// Disable data bindings during an override
bool wasApplyingDataBindings = ApplyDataBindingsEnabled;
ApplyDataBindingsEnabled = false;
TimeSpan beginTime = TimelinePosition;
if (stickToMainSegment)
{
if (!DisplayContinuously)
TimelinePosition = StartSegmentLength + timeOverride;
else
{
double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds;
if (progress > 0)
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
else
TimelinePosition = StartSegmentLength;
}
}
else
TimelinePosition = timeOverride;
double delta = (TimelinePosition - beginTime).TotalSeconds;
General.Update(delta);
Transform.Update(delta);
LayerBrush.BaseProperties?.Update(delta);
LayerBrush.Update(delta);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(delta);
baseLayerEffect.Update(delta);
}
// Restore the old data bindings enabled state
ApplyDataBindingsEnabled = wasApplyingDataBindings;
}
/// <inheritdoc />
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
public override void Reset()
{
if (_disposed)
DisplayConditionMet = false;
Timeline.JumpToStart();
}
/// <inheritdoc />
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{
if (Disposed)
throw new ObjectDisposedException("Layer");
if (!Enabled || TimelinePosition > TimelineLength)
if (!Enabled)
return;
// Ensure the layer is ready
@@ -355,8 +287,40 @@ namespace Artemis.Core
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
return;
lock (Timeline)
{
RenderLayer(Timeline, canvas);
foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
RenderLayer(extraTimeline, canvas);
Timeline.ClearDelta();
}
}
private void PrepareForRender(Timeline timeline)
{
General.Update(timeline);
Transform.Update(timeline);
LayerBrush.BaseProperties?.Update(timeline);
LayerBrush.Update(timeline.Delta.TotalSeconds);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(timeline);
baseLayerEffect.Update(timeline.Delta.TotalSeconds);
}
}
private void RenderLayer(Timeline timeline, SKCanvas canvas)
{
if (timeline.IsFinished)
return;
PrepareForRender(timeline);
if (_layerBitmap == null)
{
_layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
}
else if (_layerBitmap.Info.Width != (int) Path.Bounds.Width || _layerBitmap.Info.Height != (int) Path.Bounds.Height)
{
_layerBitmap.Dispose();
@@ -365,11 +329,7 @@ namespace Artemis.Core
using SKPath layerPath = new SKPath(Path);
using SKCanvas layerCanvas = new SKCanvas(_layerBitmap);
using SKPaint layerPaint = new SKPaint
{
FilterQuality = SKFilterQuality.Low,
Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f))
};
using SKPaint layerPaint = new SKPaint {FilterQuality = SKFilterQuality.Low};
layerCanvas.Clear();
layerPath.Transform(SKMatrix.MakeTranslation(layerPath.Bounds.Left * -1, layerPath.Bounds.Top * -1));
@@ -388,7 +348,11 @@ namespace Artemis.Core
else if (General.ResizeMode.CurrentValue == LayerResizeMode.Clip)
ClipRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath);
using SKPaint canvasPaint = new SKPaint { BlendMode = General.BlendMode.CurrentValue };
using SKPaint canvasPaint = new SKPaint
{
BlendMode = General.BlendMode.CurrentValue,
Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f))
};
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(layerCanvas, _layerBitmap.Info, layerPath, canvasPaint);
@@ -401,7 +365,7 @@ namespace Artemis.Core
(canvasPath.Bounds.Left - targetLocation.X) * -1,
(canvasPath.Bounds.Top - targetLocation.Y) * -1)
);
canvas.ClipPath(canvasPath);
// canvas.ClipPath(canvasPath);
canvas.DrawBitmap(_layerBitmap, targetLocation, canvasPaint);
}
@@ -471,11 +435,13 @@ namespace Artemis.Core
internal void CalculateRenderProperties()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
if (!Leds.Any())
{
Path = new SKPath();
}
else
{
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
@@ -498,7 +464,7 @@ namespace Artemis.Core
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
SKPoint positionProperty = Transform.Position.CurrentValue;
@@ -522,7 +488,7 @@ namespace Artemis.Core
/// <param name="path"></param>
public void IncludePathInTranslation(SKPath path, bool zeroBased)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue;
@@ -548,13 +514,51 @@ namespace Artemis.Core
}
}
/// <summary>
/// Creates a transformation matrix that applies the current transformation settings
/// </summary>
/// <param name="zeroBased">
/// If true, treats the layer as if it is located at 0,0 instead of its actual position on the
/// surface
/// </param>
/// <returns>The transformation matrix containing the current transformation settings</returns>
public SKMatrix GetTransformMatrix(bool zeroBased)
{
if (Disposed)
throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue;
float rotationProperty = Transform.Rotation.CurrentValue;
SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
if (General.ResizeMode == LayerResizeMode.Normal)
{
SKMatrix transform = SKMatrix.MakeTranslation(x, y);
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
transform = transform.PostConcat(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y));
return transform;
}
else
{
SKMatrix transform = SKMatrix.MakeTranslation(x, y);
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y));
return transform;
}
}
/// <summary>
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
/// layer translations out
/// </summary>
public void ExcludePathFromTranslation(SKPath path, bool zeroBased)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue;
@@ -590,7 +594,7 @@ namespace Artemis.Core
/// <returns>The number of transformations applied</returns>
public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue;
@@ -630,7 +634,7 @@ namespace Artemis.Core
/// <param name="led">The LED to add</param>
public void AddLed(ArtemisLed led)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
_leds.Add(led);
@@ -643,7 +647,7 @@ namespace Artemis.Core
/// <param name="leds">The LEDs to add</param>
public void AddLeds(IEnumerable<ArtemisLed> leds)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
_leds.AddRange(leds);
@@ -656,7 +660,7 @@ namespace Artemis.Core
/// <param name="led">The LED to remove</param>
public void RemoveLed(ArtemisLed led)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
_leds.Remove(led);
@@ -668,7 +672,7 @@ namespace Artemis.Core
/// </summary>
public void ClearLeds()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
_leds.Clear();
@@ -677,7 +681,7 @@ namespace Artemis.Core
internal void PopulateLeds(ArtemisSurface surface)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Layer");
List<ArtemisLed> leds = new List<ArtemisLed>();
@@ -11,7 +11,7 @@ namespace Artemis.Core
/// initialize these for you.
/// </para>
/// </summary>
public interface ILayerProperty : IStorageModel, IUpdateModel, IDisposable
public interface ILayerProperty : IStorageModel, IDisposable
{
/// <summary>
/// Gets the description attribute applied to this property
@@ -36,5 +36,11 @@ namespace Artemis.Core
/// Returns a list off all data binding registrations
/// </summary>
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
/// <summary>
/// Updates the layer properties internal state
/// </summary>
/// <param name="timeline">The timeline to apply to the property</param>
void Update(Timeline timeline);
}
}
@@ -34,20 +34,16 @@ namespace Artemis.Core
/// <inheritdoc />
public string Path { get; private set; }
/// <summary>
/// Updates the property, applying keyframes and data bindings to the current value
/// </summary>
public void Update(double deltaTime)
/// <inheritdoc />
public void Update(Timeline timeline)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
CurrentValue = BaseValue;
if (ProfileElement.ApplyKeyframesEnabled)
UpdateKeyframes();
if (ProfileElement.ApplyDataBindingsEnabled)
UpdateDataBindings(deltaTime);
UpdateKeyframes(timeline);
UpdateDataBindings(timeline);
OnUpdated();
}
@@ -125,8 +121,7 @@ namespace Artemis.Core
return;
_baseValue = value;
Update(0);
OnCurrentValueSet();
ReapplyUpdate();
}
}
@@ -169,8 +164,7 @@ namespace Artemis.Core
// Force an update so that the base value is applied to the current value and
// keyframes/data bindings are applied using the new base value
Update(0);
OnCurrentValueSet();
ReapplyUpdate();
}
/// <summary>
@@ -185,6 +179,13 @@ namespace Artemis.Core
CurrentValue = DefaultValue;
}
private void ReapplyUpdate()
{
ProfileElement.Timeline.ClearDelta();
Update(ProfileElement.Timeline);
OnCurrentValueSet();
}
#endregion
#region Keyframes
@@ -294,13 +295,13 @@ namespace Artemis.Core
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
}
private void UpdateKeyframes()
private void UpdateKeyframes(Timeline timeline)
{
if (!KeyframesSupported || !KeyframesEnabled)
return;
// The current keyframe is the last keyframe before the current time
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= ProfileElement.TimelinePosition);
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= timeline.Position);
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current
int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
@@ -314,7 +315,7 @@ namespace Artemis.Core
else
{
TimeSpan timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
float keyframeProgress = (float) ((ProfileElement.TimelinePosition - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
float keyframeProgress = (float) ((timeline.Position - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
float keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
}
@@ -416,11 +417,15 @@ namespace Artemis.Core
OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty));
}
private void UpdateDataBindings(double deltaTime)
private void UpdateDataBindings(Timeline timeline)
{
// To avoid data bindings applying at non-regular updating (during editing) only update when not overriden
if (timeline.IsOverridden)
return;
foreach (IDataBinding dataBinding in _dataBindings)
{
dataBinding.Update(deltaTime);
dataBinding.Update(timeline);
dataBinding.Apply();
}
}
@@ -452,7 +457,6 @@ namespace Artemis.Core
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
}
/// <inheritdoc />
@@ -485,12 +489,10 @@ namespace Artemis.Core
_keyframes.Clear();
try
{
_keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
_keyframes.AddRange(
Entity.KeyframeEntities.Where(k => k.Position <= ProfileElement.Timeline.Length)
.Select(k => new LayerPropertyKeyframe<T>(JsonConvert.DeserializeObject<T>(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this))
);
}
catch (JsonException e)
{
@@ -198,11 +198,12 @@ namespace Artemis.Core
layerPropertyGroup.ApplyToEntity();
}
internal void Update(double deltaTime)
internal void Update(Timeline timeline)
{
// Since at this point we don't know what properties the group has without using reflection,
// let properties subscribe to the update event and update themselves
OnPropertyGroupUpdating(new LayerPropertyGroupUpdatingEventArgs(deltaTime));
foreach (ILayerProperty layerProperty in LayerProperties)
layerProperty.Update(timeline);
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
layerPropertyGroup.Update(timeline);
}
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
@@ -266,8 +267,6 @@ namespace Artemis.Core
#region Events
internal event EventHandler<LayerPropertyGroupUpdatingEventArgs> PropertyGroupUpdating;
/// <summary>
/// Occurs when the property group has initialized all its children
/// </summary>
@@ -284,11 +283,6 @@ namespace Artemis.Core
/// </summary>
public event EventHandler VisibilityChanged;
internal virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e)
{
PropertyGroupUpdating?.Invoke(this, e);
}
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
+17 -10
View File
@@ -56,7 +56,7 @@ namespace Artemis.Core
{
lock (this)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
if (!IsActivated)
throw new ArtemisCoreException($"Cannot update inactive profile: {this}");
@@ -66,23 +66,30 @@ namespace Artemis.Core
}
}
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{
lock (this)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
if (!IsActivated)
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
foreach (ProfileElement profileElement in Children)
profileElement.Render(deltaTime, canvas, canvasInfo);
profileElement.Render(canvas, canvasInfo);
}
}
/// <inheritdoc />
public override void Reset()
{
foreach (ProfileElement child in Children)
child.Reset();
}
public Folder GetRootFolder()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
return (Folder) Children.Single();
@@ -90,7 +97,7 @@ namespace Artemis.Core
internal override void Load()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
Name = ProfileEntity.Name;
@@ -130,12 +137,12 @@ namespace Artemis.Core
ChildrenList.Clear();
IsActivated = false;
_disposed = true;
Disposed = true;
}
internal override void Save()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
ProfileEntity.Id = EntityId;
@@ -157,7 +164,7 @@ namespace Artemis.Core
{
lock (this)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
if (IsActivated)
return;
@@ -170,7 +177,7 @@ namespace Artemis.Core
internal void PopulateLeds(ArtemisSurface surface)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException("Profile");
foreach (Layer layer in GetAllLayers())
@@ -9,13 +9,13 @@ namespace Artemis.Core
{
public abstract class ProfileElement : PropertyChangedBase, IDisposable
{
protected bool _disposed;
private bool _enabled;
private Guid _entityId;
private string _name;
private int _order;
private ProfileElement _parent;
private Profile _profile;
protected bool Disposed;
protected List<ProfileElement> ChildrenList;
protected ProfileElement()
@@ -91,7 +91,12 @@ namespace Artemis.Core
/// <summary>
/// Renders the element
/// </summary>
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
/// <summary>
/// Resets the internal state of the element
/// </summary>
public abstract void Reset();
/// <inheritdoc />
public override string ToString()
@@ -108,9 +113,9 @@ namespace Artemis.Core
/// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param>
public virtual void AddChild(ProfileElement child, int? order = null)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList)
{
if (ChildrenList.Contains(child))
@@ -152,7 +157,7 @@ namespace Artemis.Core
/// <param name="child">The profile element to remove</param>
public virtual void RemoveChild(ProfileElement child)
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList)
@@ -175,7 +180,7 @@ namespace Artemis.Core
/// <returns></returns>
public List<Folder> GetAllFolders()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException(GetType().Name);
List<Folder> folders = new List<Folder>();
@@ -196,7 +201,7 @@ namespace Artemis.Core
/// <returns></returns>
public List<Layer> GetAllLayers()
{
if (_disposed)
if (Disposed)
throw new ObjectDisposedException(GetType().Name);
List<Layer> layers = new List<Layer>();
@@ -15,8 +15,7 @@ namespace Artemis.Core
{
protected RenderProfileElement()
{
ApplyDataBindingsEnabled = true;
ApplyKeyframesEnabled = true;
Timeline = new Timeline();
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@@ -24,49 +23,21 @@ namespace Artemis.Core
public abstract List<ILayerProperty> GetAllLayerProperties();
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
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;
DisplayCondition = RenderElementEntity.DisplayCondition != null
? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
: new DataModelConditionGroup(null);
Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline)
: new Timeline();
ActivateEffects();
}
internal void SaveRenderElement()
{
RenderElementEntity.StartSegmentLength = StartSegmentLength;
RenderElementEntity.MainSegmentLength = MainSegmentLength;
RenderElementEntity.EndSegmentLength = EndSegmentLength;
RenderElementEntity.DisplayContinuously = DisplayContinuously;
RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline;
RenderElementEntity.LayerEffects.Clear();
foreach (BaseLayerEffect layerEffect in LayerEffects)
{
@@ -87,8 +58,48 @@ namespace Artemis.Core
// Conditions
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
DisplayCondition?.Save();
// Timeline
RenderElementEntity.Timeline = Timeline?.Entity;
Timeline?.Save();
}
#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 status
/// </summary>
public void UpdateTimeline(double deltaTime)
{
// The play mode dictates whether to stick to the main segment unless the display conditions contains events
bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet;
if (DisplayCondition != null && DisplayCondition.ContainsEvents)
stickToMainSegment = false;
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
}
#endregion
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
#region Properties
private SKPath _path;
@@ -141,126 +152,6 @@ namespace Artemis.Core
#endregion
#region Timeline
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
private bool _displayContinuously;
private bool _alwaysFinishTimeline;
/// <summary>
/// Gets or sets the length of the start segment
/// </summary>
public TimeSpan StartSegmentLength
{
get => _startSegmentLength;
set
{
if (!SetAndNotify(ref _startSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
}
/// <summary>
/// Gets or sets the length of the main segment
/// </summary>
public TimeSpan MainSegmentLength
{
get => _mainSegmentLength;
set
{
if (!SetAndNotify(ref _mainSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
}
/// <summary>
/// Gets or sets the length of the end segment
/// </summary>
public TimeSpan EndSegmentLength
{
get => _endSegmentLength;
set
{
if (!SetAndNotify(ref _endSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
}
/// <summary>
/// Gets the current timeline position
/// </summary>
public TimeSpan TimelinePosition
{
get => _timelinePosition;
protected set => SetAndNotify(ref _timelinePosition, value);
}
/// <summary>
/// Gets or sets whether main timeline should repeat itself as long as display conditions are met
/// </summary>
public bool DisplayContinuously
{
get => _displayContinuously;
set => SetAndNotify(ref _displayContinuously, value);
}
/// <summary>
/// Gets or sets whether the timeline should finish when conditions are no longer met
/// </summary>
public bool AlwaysFinishTimeline
{
get => _alwaysFinishTimeline;
set => SetAndNotify(ref _alwaysFinishTimeline, value);
}
/// <summary>
/// Gets the max length of this element and any of its children
/// </summary>
public TimeSpan TimelineLength { get; protected set; }
protected double UpdateTimeline(double deltaTime)
{
TimeSpan oldPosition = _timelinePosition;
TimeSpan deltaTimeSpan = TimeSpan.FromSeconds(deltaTime);
TimeSpan 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;
}
else
{
// Skip to the last segment if conditions are no longer met
if (!AlwaysFinishTimeline && TimelinePosition < mainSegmentEnd)
TimelinePosition = mainSegmentEnd;
}
return (TimelinePosition - oldPosition).TotalSeconds;
}
protected internal abstract void UpdateTimelineLength();
/// <summary>
/// Overrides the progress of the element
/// </summary>
/// <param name="timeOverride"></param>
/// <param name="stickToMainSegment"></param>
public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
#endregion
#region Effect management
protected List<BaseLayerEffect> _layerEffects;
@@ -392,11 +283,10 @@ namespace Artemis.Core
public bool DisplayConditionMet
{
get => _displayConditionMet;
private set => SetAndNotify(ref _displayConditionMet, value);
protected set => SetAndNotify(ref _displayConditionMet, value);
}
private DataModelConditionGroup _displayCondition;
private TimeSpan _timelinePosition;
private bool _displayConditionMet;
/// <summary>
@@ -409,20 +299,46 @@ namespace Artemis.Core
}
/// <summary>
/// Gets or sets whether keyframes should be applied when this profile element updates
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline"/>
/// </summary>
public bool ApplyKeyframesEnabled { get; set; }
/// <summary>
/// Gets or sets whether data bindings should be applied when this profile element updates
/// </summary>
public bool ApplyDataBindingsEnabled { get; set; }
public void UpdateDisplayCondition()
{
bool conditionMet = DisplayCondition == null || DisplayCondition.Evaluate();
if (conditionMet && !DisplayConditionMet)
TimelinePosition = TimeSpan.Zero;
if (DisplayCondition == null)
{
DisplayConditionMet = true;
return;
}
bool conditionMet = DisplayCondition.Evaluate();
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 && DisplayConditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd)
Timeline.JumpToEndSegment();
}
else if (conditionMet)
{
// 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 = conditionMet;
}
+493
View File
@@ -0,0 +1,493 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Storage.Entities.Profile;
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// Represents a timeline used by profile elements
/// </summary>
public class Timeline : PropertyChangedBase, IStorageModel
{
/// <summary>
/// Creates a new instance of the <see cref="Timeline" /> class
/// </summary>
public Timeline()
{
Entity = new TimelineEntity();
_extraTimelines = new List<Timeline>();
MainSegmentLength = TimeSpan.FromSeconds(5);
Save();
}
internal Timeline(TimelineEntity entity)
{
Entity = entity;
_extraTimelines = new List<Timeline>();
Load();
}
private Timeline(Timeline parent)
{
Parent = parent;
}
#region Extra timelines
/// <summary>
/// Adds an extra timeline to this timeline
/// </summary>
public void AddExtraTimeline()
{
_extraTimelines.Add(new Timeline(this));
}
/// <summary>
/// Removes all extra timelines from this timeline
/// </summary>
public void ClearExtraTimelines()
{
_extraTimelines.Clear();
}
#endregion
#region Properties
private TimeSpan _position;
private TimeSpan _lastDelta;
private TimeLineEventOverlapMode _eventOverlapMode;
private TimelinePlayMode _playMode;
private TimelineStopMode _stopMode;
private readonly List<Timeline> _extraTimelines;
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
/// <summary>
/// Gets the parent this timeline is an extra timeline of
/// </summary>
public Timeline? Parent { get; }
/// <summary>
/// Gets the current position of the timeline
/// </summary>
public TimeSpan Position
{
get => _position;
private set => SetAndNotify(ref _position, value);
}
/// <summary>
/// Gets the cumulative delta of all calls to <see cref="Update" /> that took place after the last call to <see cref="ClearDelta"/>
/// <para>
/// Note: If this is an extra timeline <see cref="Delta" /> is always equal to <see cref="DeltaToParent" />
/// </para>
/// </summary>
public TimeSpan Delta
{
get => Parent == null ? _lastDelta : DeltaToParent;
private set => SetAndNotify(ref _lastDelta, value);
}
/// <summary>
/// Gets the delta to this timeline's <see cref="Parent" />
/// </summary>
public TimeSpan DeltaToParent => Parent != null ? Position - Parent.Position : TimeSpan.Zero;
/// <summary>
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
/// </summary>
public TimelinePlayMode PlayMode
{
get => _playMode;
set => SetAndNotify(ref _playMode, value);
}
/// <summary>
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
/// </summary>
public TimelineStopMode StopMode
{
get => _stopMode;
set => SetAndNotify(ref _stopMode, value);
}
/// <summary>
/// Gets or sets the mode in which the render element responds to display condition events being fired before the
/// timeline finished
/// </summary>
public TimeLineEventOverlapMode EventOverlapMode
{
get => _eventOverlapMode;
set => SetAndNotify(ref _eventOverlapMode, value);
}
/// <summary>
/// Gets a list of extra copies of the timeline applied to this timeline
/// </summary>
public ReadOnlyCollection<Timeline> ExtraTimelines => _extraTimelines.AsReadOnly();
/// <summary>
/// Gets a boolean indicating whether the timeline has finished its run
/// </summary>
public bool IsFinished => Position > Length || Length == TimeSpan.Zero;
/// <summary>
/// Gets a boolean indicating whether the timeline progress has been overridden
/// </summary>
public bool IsOverridden { get; private set; }
#region Segments
/// <summary>
/// Gets the total length of this timeline
/// </summary>
public TimeSpan Length => StartSegmentLength + MainSegmentLength + EndSegmentLength;
/// <summary>
/// Gets or sets the length of the start segment
/// </summary>
public TimeSpan StartSegmentLength
{
get => _startSegmentLength;
set
{
if (SetAndNotify(ref _startSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.Start, false);
}
}
/// <summary>
/// Gets or sets the length of the main segment
/// </summary>
public TimeSpan MainSegmentLength
{
get => _mainSegmentLength;
set
{
if (SetAndNotify(ref _mainSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.Main, false);
}
}
/// <summary>
/// Gets or sets the length of the end segment
/// </summary>
public TimeSpan EndSegmentLength
{
get => _endSegmentLength;
set
{
if (SetAndNotify(ref _endSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.End, false);
}
}
/// <summary>
/// Gets or sets the start position of the main segment
/// </summary>
public TimeSpan MainSegmentStartPosition
{
get => StartSegmentEndPosition;
set
{
StartSegmentEndPosition = value;
NotifySegmentShiftAt(TimelineSegment.Main, true);
}
}
/// <summary>
/// Gets or sets the end position of the end segment
/// </summary>
public TimeSpan EndSegmentStartPosition
{
get => MainSegmentEndPosition;
set
{
MainSegmentEndPosition = value;
NotifySegmentShiftAt(TimelineSegment.End, true);
}
}
/// <summary>
/// Gets or sets the end position of the start segment
/// </summary>
public TimeSpan StartSegmentEndPosition
{
get => StartSegmentLength;
set
{
StartSegmentLength = value;
NotifySegmentShiftAt(TimelineSegment.Start, false);
}
}
/// <summary>
/// Gets or sets the end position of the main segment
/// </summary>
public TimeSpan MainSegmentEndPosition
{
get => StartSegmentEndPosition + MainSegmentLength;
set
{
MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero;
NotifySegmentShiftAt(TimelineSegment.Main, false);
}
}
/// <summary>
/// Gets or sets the end position of the end segment
/// </summary>
public TimeSpan EndSegmentEndPosition
{
get => MainSegmentEndPosition + EndSegmentLength;
set
{
EndSegmentLength = value - MainSegmentEndPosition >= TimeSpan.Zero ? value - MainSegmentEndPosition : TimeSpan.Zero;
NotifySegmentShiftAt(TimelineSegment.End, false);
}
}
internal TimelineEntity Entity { get; set; }
/// <summary>
/// Notifies the right segments in a way that I don't have to think about it
/// </summary>
/// <param name="segment">The segment that was updated</param>
/// <param name="startUpdated">Whether the start point of the <paramref name="segment" /> was updated</param>
private void NotifySegmentShiftAt(TimelineSegment segment, bool startUpdated)
{
if (segment <= TimelineSegment.End)
{
if (startUpdated || segment < TimelineSegment.End)
NotifyOfPropertyChange(nameof(EndSegmentStartPosition));
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
}
if (segment <= TimelineSegment.Main)
{
if (startUpdated || segment < TimelineSegment.Main)
NotifyOfPropertyChange(nameof(MainSegmentStartPosition));
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
}
if (segment <= TimelineSegment.Start) NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
NotifyOfPropertyChange(nameof(Length));
}
#endregion
#endregion
#region Updating
/// <summary>
/// Updates the timeline, applying the provided <paramref name="delta" /> to the <see cref="Position" />
/// </summary>
/// <param name="delta">The amount of time to apply to the position</param>
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
public void Update(TimeSpan delta, bool stickToMainSegment)
{
lock (this)
{
Delta += delta;
Position += delta;
IsOverridden = false;
if (stickToMainSegment && Position >= MainSegmentStartPosition)
{
// If the main segment has no length, simply stick to the start of the segment
if (MainSegmentLength == TimeSpan.Zero)
Position = MainSegmentStartPosition;
// Ensure wrapping back around retains the delta time
else
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
}
foreach (Timeline extraTimeline in _extraTimelines)
extraTimeline.Update(delta, false);
}
}
/// <summary>
/// Moves the position of the timeline backwards to the very start of the timeline
/// </summary>
public void JumpToStart()
{
lock (this)
{
if (Position == TimeSpan.Zero)
return;
Delta = TimeSpan.Zero - Position;
Position = TimeSpan.Zero;
_extraTimelines.Clear();
}
}
/// <summary>
/// Moves the position of the timeline forwards to the beginning of the end segment
/// </summary>
public void JumpToEndSegment()
{
lock (this)
{
if (Position >= EndSegmentStartPosition)
return;
Delta = EndSegmentStartPosition - Position;
Position = EndSegmentStartPosition;
_extraTimelines.Clear();
}
}
/// <summary>
/// Moves the position of the timeline forwards to the very end of the timeline
/// </summary>
public void JumpToEnd()
{
lock (this)
{
if (Position >= EndSegmentEndPosition)
return;
Delta = EndSegmentEndPosition - Position;
Position = EndSegmentEndPosition;
_extraTimelines.Clear();
}
}
/// <summary>
/// Overrides the <see cref="Position" /> to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
public void Override(TimeSpan position, bool stickToMainSegment)
{
lock (this)
{
Delta += position - Position;
Position = position;
IsOverridden = true;
if (stickToMainSegment && Position >= MainSegmentStartPosition)
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
_extraTimelines.Clear();
}
}
/// <summary>
/// Sets the <see cref="Delta" /> to <see cref="TimeSpan.Zero" />
/// </summary>
public void ClearDelta()
{
lock (this)
{
Delta = TimeSpan.Zero;
}
}
#endregion
#region Storage
/// <inheritdoc />
public void Load()
{
StartSegmentLength = Entity.StartSegmentLength;
MainSegmentLength = Entity.MainSegmentLength;
EndSegmentLength = Entity.EndSegmentLength;
PlayMode = (TimelinePlayMode) Entity.PlayMode;
StopMode = (TimelineStopMode) Entity.StopMode;
EventOverlapMode = (TimeLineEventOverlapMode) Entity.EventOverlapMode;
}
/// <inheritdoc />
public void Save()
{
Entity.StartSegmentLength = StartSegmentLength;
Entity.MainSegmentLength = MainSegmentLength;
Entity.EndSegmentLength = EndSegmentLength;
Entity.PlayMode = (int) PlayMode;
Entity.StopMode = (int) StopMode;
Entity.EventOverlapMode = (int) EventOverlapMode;
}
#endregion
public override string ToString()
{
return $"Progress: {Position}/{Length} - delta: {Delta}";
}
}
internal enum TimelineSegment
{
Start,
Main,
End
}
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions are met
/// </summary>
public enum TimelinePlayMode
{
/// <summary>
/// Continue repeating the main segment of the timeline while the condition is met
/// </summary>
Repeat,
/// <summary>
/// Only play the timeline once when the condition is met
/// </summary>
Once
}
/// <summary>
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
/// </summary>
public enum TimelineStopMode
{
/// <summary>
/// When conditions are no longer met, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When conditions are no longer met, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions events are fired
/// </summary>
public enum TimeLineEventOverlapMode
{
/// <summary>
/// Stop the current run and restart the timeline
/// </summary>
Restart,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore,
/// <summary>
/// Play another copy of the timeline on top of the current run
/// </summary>
Copy
}
}
@@ -81,8 +81,9 @@ namespace Artemis.Core.Modules
internal override void InternalDisablePlugin()
{
DataModel = null;
Deactivate(true);
base.InternalDisablePlugin();
DataModel = null;
}
}
@@ -112,7 +113,7 @@ namespace Artemis.Core.Modules
/// <summary>
/// Gets the currently active profile
/// </summary>
public Profile ActiveProfile { get; private set; }
public Profile? ActiveProfile { get; private set; }
/// <summary>
/// Disables updating the profile, rendering does continue
@@ -174,7 +175,7 @@ namespace Artemis.Core.Modules
lock (this)
{
// Render the profile
ActiveProfile?.Render(deltaTime, canvas, canvasInfo);
ActiveProfile?.Render(canvas, canvasInfo);
}
ProfileRendered(deltaTime, surface, canvas, canvasInfo);
+1 -1
View File
@@ -43,7 +43,7 @@ namespace Artemis.Core
/// The action to call every time the interval has passed. The delta time parameter represents the
/// time passed since the last update in seconds
/// </param>
/// <returns>The resulting plugin update registration</returns>
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
{
if (action == null)
+355 -163
View File
@@ -1,47 +1,55 @@
{
"$type": "Artemis.Storage.Entities.Profile.ProfileEntity, Artemis.Storage",
"Id": "eb4f487b-475b-408f-a84f-733412d41b44",
"Id": "824a235d-da46-4c82-a16b-13efe347f492",
"PluginGuid": "0de2991a-d7b8-4f61-ae4e-6623849215b5",
"Name": "Intro animation",
"Name": "Intro animation - Imported",
"IsActive": true,
"Folders": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage",
"Id": "cc21b67c-3485-4dc6-b2af-105fda42a915",
"ParentId": "eb4f487b-475b-408f-a84f-733412d41b44",
"ParentId": "824a235d-da46-4c82-a16b-13efe347f492",
"Order": 1,
"Name": "Root folder",
"Enabled": true,
"Profile": null,
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:05",
"EndSegmentLength": "00:00:00",
"DisplayContinuously": true,
"AlwaysFinishTimeline": false,
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
"LayerEffects": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"PropertyEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"ExpandedPropertyGroups": {
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
"$values": []
},
"DisplayCondition": {
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
"BooleanOperator": 0,
"Children": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.Abstract.DataModelConditionPartEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
"Timeline": {
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:05",
"EndSegmentLength": "00:00:00",
"PlayMode": 0,
"StopMode": 0,
"EventOverlapMode": 0
}
}
]
},
"Layers": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage",
@@ -51,25 +59,17 @@
"Name": "Noise",
"Enabled": true,
"Leds": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"Profile": null,
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:05",
"EndSegmentLength": "00:00:00",
"DisplayContinuously": false,
"AlwaysFinishTimeline": false,
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
"LayerEffects": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"PropertyEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
@@ -78,8 +78,11 @@
"Value": "1",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -90,8 +93,11 @@
"Value": "1",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -102,8 +108,11 @@
"Value": "3",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -114,8 +123,11 @@
"Value": "{\"BrushPluginGuid\":\"61cbbf01-8d69-4ede-a972-f3f269da66d9\",\"BrushType\":\"NoiseBrush\"}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -126,8 +138,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -138,8 +153,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -150,8 +168,7 @@
"Value": "{\"IsEmpty\":false,\"Width\":500.0,\"Height\":500.0}",
"KeyframesEnabled": true,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
@@ -168,6 +185,10 @@
"EasingFunction": 0
}
]
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
{
@@ -177,8 +198,11 @@
"Value": "-45.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -189,9 +213,15 @@
"Value": "100.0",
"KeyframesEnabled": true,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
"Position": "00:00:03.2500000",
"Timeline": 0,
"Value": "100.0",
"EasingFunction": 0
},
{
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
"Position": "00:00:04",
@@ -207,6 +237,10 @@
"EasingFunction": 0
}
]
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
{
@@ -216,8 +250,11 @@
"Value": "1",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -228,8 +265,11 @@
"Value": "\"#ff009688\"",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -240,8 +280,11 @@
"Value": "\"#ff00ffe7\"",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -249,12 +292,14 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9",
"Path": "LayerBrush.GradientColor",
"Value":
"{\"Stops\":[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}],\"Rotation\":0.0}",
"Value": "{\"Stops\":[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}]}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -265,8 +310,11 @@
"Value": "{\"IsEmpty\":false,\"Width\":44.9,\"Height\":31.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -277,8 +325,11 @@
"Value": "228.5",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -289,8 +340,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -301,8 +355,11 @@
"Value": "25.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
}
@@ -311,8 +368,25 @@
"ExpandedPropertyGroups": {
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
"$values": [
"LayerBrush"
"Transform"
]
},
"DisplayCondition": {
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
"BooleanOperator": 0,
"Children": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.Abstract.DataModelConditionPartEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
"Timeline": {
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:05",
"EndSegmentLength": "00:00:00",
"PlayMode": 0,
"StopMode": 0,
"EventOverlapMode": 0
}
},
{
@@ -323,25 +397,17 @@
"Name": "Exploison",
"Enabled": true,
"Leds": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"Profile": null,
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:03",
"EndSegmentLength": "00:00:00",
"DisplayContinuously": false,
"AlwaysFinishTimeline": false,
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
"LayerEffects": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"PropertyEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
@@ -350,8 +416,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -362,8 +431,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -374,8 +446,11 @@
"Value": "3",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -386,8 +461,11 @@
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -398,8 +476,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -410,8 +491,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -422,8 +506,7 @@
"Value": "{\"IsEmpty\":false,\"Width\":110.03,\"Height\":340.37}",
"KeyframesEnabled": true,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
@@ -440,6 +523,10 @@
"EasingFunction": 0
}
]
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
{
@@ -449,8 +536,11 @@
"Value": "0.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -461,8 +551,11 @@
"Value": "100.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -473,8 +566,11 @@
"Value": "2",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -485,8 +581,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -497,8 +596,11 @@
"Value": "\"#ffff0000\"",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -506,12 +608,14 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
"Path": "LayerBrush.Colors",
"Value":
"{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.492},{\"Color\":\"#ffedff00\",\"Position\":0.905},{\"Color\":\"#00ff0000\",\"Position\":1.0}],\"Rotation\":0.0}",
"Value": "{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.492},{\"Color\":\"#ffedff00\",\"Position\":0.905},{\"Color\":\"#00ff0000\",\"Position\":1.0}]}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -522,8 +626,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -534,8 +641,11 @@
"Value": "0.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -546,8 +656,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -558,8 +671,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
}
@@ -568,35 +684,44 @@
"ExpandedPropertyGroups": {
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
"$values": []
},
"DisplayCondition": {
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
"BooleanOperator": 0,
"Children": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.Abstract.DataModelConditionPartEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
"Timeline": {
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:02.8500000",
"EndSegmentLength": "00:00:00",
"PlayMode": 1,
"StopMode": 0,
"EventOverlapMode": 0
}
},
{
"$type": "Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage",
"Id": "f046f56f-a236-4ed6-bbd9-b5a4731878cf",
"ParentId": "cc21b67c-3485-4dc6-b2af-105fda42a915",
"Order": 2,
"Order": 3,
"Name": "Background",
"Enabled": true,
"Leds": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"Profile": null,
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:03",
"EndSegmentLength": "00:00:00",
"DisplayContinuously": false,
"AlwaysFinishTimeline": false,
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
"LayerEffects": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"PropertyEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": [
{
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
@@ -605,8 +730,11 @@
"Value": "1",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -617,8 +745,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -629,8 +760,11 @@
"Value": "3",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -641,8 +775,11 @@
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -653,8 +790,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -665,8 +805,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -677,8 +820,11 @@
"Value": "{\"IsEmpty\":false,\"Width\":100.0,\"Height\":100.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -689,8 +835,11 @@
"Value": "0.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -701,8 +850,11 @@
"Value": "100.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -713,8 +865,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -725,8 +880,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -737,8 +895,11 @@
"Value": "\"#ff000000\"",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -746,12 +907,14 @@
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
"Path": "LayerBrush.Colors",
"Value":
"{\"Stops\":[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.125},{\"Color\":\"#ffedff00\",\"Position\":0.25},{\"Color\":\"#ff65ff00\",\"Position\":0.375},{\"Color\":\"#ff00ff22\",\"Position\":0.5},{\"Color\":\"#ff00ffaa\",\"Position\":0.625},{\"Color\":\"#ff00cbff\",\"Position\":0.75},{\"Color\":\"#ff0043ff\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}],\"Rotation\":0.0}",
"Value": "{\"Stops\":[{\"Color\":\"#ffff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.125},{\"Color\":\"#ffedff00\",\"Position\":0.25},{\"Color\":\"#ff65ff00\",\"Position\":0.375},{\"Color\":\"#ff00ff22\",\"Position\":0.5},{\"Color\":\"#ff00ffaa\",\"Position\":0.625},{\"Color\":\"#ff00cbff\",\"Position\":0.75},{\"Color\":\"#ff0043ff\",\"Position\":0.875},{\"Color\":\"#ffff0000\",\"Position\":1.0}]}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -762,8 +925,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -774,8 +940,11 @@
"Value": "0.0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -786,8 +955,11 @@
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
@@ -798,8 +970,11 @@
"Value": "0",
"KeyframesEnabled": false,
"KeyframeEntities": {
"$type":
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
},
"DataBindingEntities": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.DataBindings.DataBindingEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
}
@@ -810,6 +985,23 @@
"$values": [
"LayerBrush"
]
},
"DisplayCondition": {
"$type": "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGroupEntity, Artemis.Storage",
"BooleanOperator": 0,
"Children": {
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.Abstract.DataModelConditionPartEntity, Artemis.Storage]], System.Private.CoreLib",
"$values": []
}
},
"Timeline": {
"$type": "Artemis.Storage.Entities.Profile.TimelineEntity, Artemis.Storage",
"StartSegmentLength": "00:00:00",
"MainSegmentLength": "00:00:05",
"EndSegmentLength": "00:00:00",
"PlayMode": 0,
"StopMode": 0,
"EventOverlapMode": 0
}
}
]
+2 -2
View File
@@ -123,7 +123,7 @@ namespace Artemis.Core.Services
_introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info);
}
TimeSpan introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.TimelineLength);
TimeSpan introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.Timeline.Length);
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
Task.Run(async () =>
@@ -146,7 +146,7 @@ namespace Artemis.Core.Services
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> {new SKColorConverter()}
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
};
}
+1 -2
View File
@@ -221,8 +221,7 @@ namespace Artemis.Core.Services
Type pluginType = pluginTypes.Single();
try
{
IParameter[] parameters = new IParameter[]
{
IParameter[] parameters = {
new Parameter("PluginInfo", pluginInfo, false)
};
pluginInfo.Kernel = new ChildKernel(_kernel);
@@ -72,6 +72,8 @@ namespace Artemis.Core.Services
// Colors
RegisterModifierType(Constants.CorePluginInfo, new SKColorSumModifierType());
RegisterModifierType(Constants.CorePluginInfo, new SKColorSaturateModifierType());
RegisterModifierType(Constants.CorePluginInfo, new SKColorDesaturateModifierType());
RegisterModifierType(Constants.CorePluginInfo, new SKColorBrightenModifierType());
RegisterModifierType(Constants.CorePluginInfo, new SKColorDarkenModifierType());
RegisterModifierType(Constants.CorePluginInfo, new SKColorRotateHueModifierType());
@@ -49,6 +49,11 @@ namespace Artemis.Core.Services
/// <param name="profileModule"></param>
void ActivateLastProfile(ProfileModule profileModule);
/// <summary>
/// Reloads the currently active profile on the provided profile module
/// </summary>
void ReloadProfile(ProfileModule module);
/// <summary>
/// Asynchronously activates the last profile of the given profile module using a fade animation
/// </summary>
@@ -82,6 +82,19 @@ namespace Artemis.Core.Services
return profile;
}
public void ReloadProfile(ProfileModule module)
{
if (module.ActiveProfile == null)
return;
ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId);
Profile profile = new Profile(module, entity);
InstantiateProfile(profile);
module.ChangeActiveProfile(null, _surfaceService.ActiveSurface);
module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
}
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
{
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
@@ -24,17 +24,6 @@ namespace Artemis.Core
);
}
public static void LogListPredicateDeserializationFailure(DataModelConditionListPredicate dataModelConditionPredicate, JsonException exception)
{
_logger.Warning(
exception,
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
dataModelConditionPredicate.Entity.LeftPath?.Path,
dataModelConditionPredicate.Entity.OperatorType,
dataModelConditionPredicate.Entity.RightPath?.Path
);
}
public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception)
{
_logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName);
+1 -1
View File
@@ -32,7 +32,7 @@ namespace Artemis.Core
return;
AnimationProfile.Update(deltaTime);
AnimationProfile.Render(deltaTime, canvas, bitmapInfo);
AnimationProfile.Render(canvas, bitmapInfo);
}
private void CreateIntroProfile()