diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
index 06398867e..705d6dc5c 100644
--- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings
+++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
@@ -1,4 +1,5 @@
+ True
True
True
True
diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs
new file mode 100644
index 000000000..2c27d58d1
--- /dev/null
+++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs
@@ -0,0 +1,19 @@
+using SkiaSharp;
+
+namespace Artemis.Core.DefaultTypes
+{
+ internal class SKColorDesaturateModifierType : DataBindingModifierType
+ {
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs
new file mode 100644
index 000000000..daa5b0fc7
--- /dev/null
+++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs
@@ -0,0 +1,18 @@
+using SkiaSharp;
+
+namespace Artemis.Core.DefaultTypes
+{
+ internal class SKColorSaturateModifierType : DataBindingModifierType
+ {
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs b/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
new file mode 100644
index 000000000..77a09cb2b
--- /dev/null
+++ b/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Artemis.Core.JsonConverters
+{
+ ///
+ /// An int converter that, if required, will round float values
+ ///
+ internal class ForgivingIntConverter : JsonConverter
+ {
+ 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(reader);
+
+ if (jsonValue.Type == JTokenType.Float)
+ return (int) Math.Round(jsonValue.Value());
+ if (jsonValue.Type == JTokenType.Integer)
+ return jsonValue.Value();
+
+ throw new FormatException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/IUpdateModel.cs b/src/Artemis.Core/Models/IUpdateModel.cs
index 870079a7f..24c480c73 100644
--- a/src/Artemis.Core/Models/IUpdateModel.cs
+++ b/src/Artemis.Core/Models/IUpdateModel.cs
@@ -8,7 +8,7 @@
///
/// Performs an update on the model
///
- /// The delta time in seconds
- void Update(double deltaTime);
+ /// The timeline to apply during update
+ void Update(Timeline timeline);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
index 416f41912..2134cbaf9 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
@@ -7,37 +7,51 @@ namespace Artemis.Core
///
public abstract class ConditionOperator : BaseConditionOperator
{
+ ///
+ public override Type LeftSideType => typeof(TLeftSide);
+
+ ///
+ public override Type RightSideType => typeof(TRightSide);
+
///
/// Evaluates the operator on a and b
///
/// The parameter on the left side of the expression
/// The parameter on the right side of the expression
public abstract bool Evaluate(TLeftSide a, TRightSide b);
-
+
///
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!);
}
-
- ///
- public override Type LeftSideType => typeof(TLeftSide);
-
- ///
- public override Type RightSideType => typeof(TRightSide);
}
///
@@ -45,6 +59,14 @@ namespace Artemis.Core
///
public abstract class ConditionOperator : BaseConditionOperator
{
+ ///
+ public override Type LeftSideType => typeof(TLeftSide);
+
+ ///
+ /// Always null, not applicable to this type of condition operator
+ ///
+ public override Type? RightSideType => null;
+
///
/// Evaluates the operator on a and b
///
@@ -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!);
}
-
- ///
- public override Type LeftSideType => typeof(TLeftSide);
-
- ///
- /// Always null, not applicable to this type of condition operator
- ///
- public override Type? RightSideType => null;
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs
index bca0ebc4d..c42ef85ae 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs
@@ -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();
}
}
+ ///
+ /// Removes all children. You monster.
+ ///
+ public void ClearChildren()
+ {
+ while (Children.Any())
+ RemoveChild(Children[0]);
+ }
+
///
/// Evaluates the condition part on the data model
///
@@ -71,7 +84,7 @@ namespace Artemis.Core
#region IDisposable
///
- /// Disposed the condition part
+ /// Disposed the condition part
///
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
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs
similarity index 86%
rename from src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs
rename to src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs
index db96b8c22..f99364cd2 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs
@@ -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
///
- public class DataModelConditionPredicate : DataModelConditionPart
+ public abstract class DataModelConditionPredicate : DataModelConditionPart
{
///
/// Creates a new instance of the class
///
///
///
- public DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
+ /// A new empty entity
+ 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();
}
///
@@ -44,31 +39,117 @@ namespace Artemis.Core
///
/// Gets the operator
///
- public BaseConditionOperator? Operator { get; private set; }
+ public BaseConditionOperator? Operator { get; protected set; }
///
/// Gets the path of the left property
///
- public DataModelPath? LeftPath { get; private set; }
+ public DataModelPath? LeftPath { get; protected set; }
///
/// Gets the path of the right property
///
- public DataModelPath? RightPath { get; private set; }
+ public DataModelPath? RightPath { get; protected set; }
///
/// Gets the right static value, only used it is
///
///
- public object? RightStaticValue { get; private set; }
+ public object? RightStaticValue { get; protected set; }
internal DataModelConditionPredicateEntity Entity { get; set; }
+ ///
+ public override string ToString()
+ {
+ if (PredicateType == ProfileRightSideType.Dynamic)
+ return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
+ return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
+ }
+
+ #region IDisposable
+
+ ///
+ 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
+
///
/// Updates the left side of the predicate
///
/// The path pointing to the left side value inside the data model
- 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();
}
- ///
- 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());
- }
-
- ///
- public override string ToString()
- {
- if (PredicateType == ProfileRightSideType.Dynamic)
- return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
- return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
- ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
-
- LeftPath?.Dispose();
- RightPath?.Dispose();
-
- base.Dispose(disposing);
- }
-
- ///
- 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;
- }
+ ///
+ /// Determines the best type to use for the right side op this predicate
+ ///
+ public abstract Type? GetPreferredRightSideType();
private void ValidateOperator()
{
@@ -327,6 +283,76 @@ namespace Artemis.Core
}
}
+ #endregion
+
+ #region Evaluation
+
+ ///
+ 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());
+ }
+
+ ///
+ 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)
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs
new file mode 100644
index 000000000..c629ef276
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs
@@ -0,0 +1,241 @@
+using System;
+using System.Linq;
+using Artemis.Storage.Entities.Profile.Abstract;
+using Artemis.Storage.Entities.Profile.Conditions;
+
+namespace Artemis.Core
+{
+ ///
+ /// A condition that evaluates to true when an event is triggered
+ ///
+ public class DataModelConditionEvent : DataModelConditionPart
+ {
+ private bool _disposed;
+ private bool _reinitializing;
+ private IDataModelEvent? _event;
+ private bool _eventTriggered;
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ ///
+ public DataModelConditionEvent(DataModelConditionPart parent)
+ {
+ Parent = parent;
+ Entity = new DataModelConditionEventEntity();
+
+ Initialize();
+ }
+
+ internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
+ {
+ Parent = parent;
+ Entity = entity;
+
+ Initialize();
+ }
+
+ ///
+ /// Gets the path of the event property
+ ///
+ public DataModelPath? EventPath { get; private set; }
+
+ public Type? EventArgumentType { get; set; }
+
+ internal DataModelConditionEventEntity Entity { get; set; }
+
+ ///
+ 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;
+ }
+
+ ///
+ /// Updates the event the condition is triggered by
+ ///
+ 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
+
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs
new file mode 100644
index 000000000..4e5f38d02
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs
@@ -0,0 +1,161 @@
+using System;
+using Artemis.Storage.Entities.Profile;
+using Artemis.Storage.Entities.Profile.Conditions;
+
+namespace Artemis.Core
+{
+ ///
+ /// A predicate like evaluated inside a
+ ///
+ public class DataModelConditionEventPredicate : DataModelConditionPredicate
+ {
+ ///
+ /// Creates a new instance of the class
+ ///
+ ///
+ ///
+ 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();
+ }
+
+ ///
+ /// Gets the data model condition event this predicate belongs to
+ ///
+ 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
+
+ ///
+ 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
+
+ ///
+ /// Not supported for event predicates, always returns false
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs
new file mode 100644
index 000000000..2f29d2f40
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs
@@ -0,0 +1,63 @@
+using System;
+using Artemis.Storage.Entities.Profile.Conditions;
+
+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
+ ///
+ public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
+ {
+ ///
+ /// Creates a new instance of the class
+ ///
+ ///
+ ///
+ public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
+ : base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
+ {
+ Initialize();
+ }
+
+ internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
+ : base(parent, entity)
+ {
+ Initialize();
+ }
+
+ #region Modification
+
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs
index c731176ca..2b44acb40 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs
@@ -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;
}
///
@@ -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;
}
///
@@ -52,6 +64,11 @@ namespace Artemis.Core
///
public BooleanOperator BooleanOperator { get; set; }
+ ///
+ /// Gets whether this group contains any events
+ ///
+ public bool ContainsEvents { get; private set; }
+
internal DataModelConditionGroupEntity Entity { get; set; }
///
@@ -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 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);
+ }
}
///
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
index a3459f385..392e1ba51 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
@@ -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;
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs
index 601483aef..32316ec38 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs
@@ -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
{
///
/// A predicate like evaluated inside a
///
- public class DataModelConditionListPredicate : DataModelConditionPart
+ public class DataModelConditionListPredicate : DataModelConditionPredicate
{
///
/// Creates a new instance of the class
@@ -18,138 +15,104 @@ namespace Artemis.Core
///
///
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();
}
- ///
- /// Gets or sets the predicate type
- ///
- public ProfileRightSideType PredicateType { get; set; }
-
- ///
- /// Gets the operator
- ///
- public BaseConditionOperator? Operator { get; private set; }
-
///
/// Gets the data model condition list this predicate belongs to
///
public DataModelConditionList DataModelConditionList { get; private set; }
- ///
- /// Gets the path of the left property
- ///
- public DataModelPath? LeftPath { get; private set; }
-
- ///
- /// Gets the path of the right property
- ///
- public DataModelPath? RightPath { get; private set; }
-
- ///
- /// Gets the right static value, only used it is
- ///
- ///
- public object? RightStaticValue { get; private set; }
-
- internal DataModelConditionListPredicateEntity Entity { get; set; }
-
- ///
- /// Updates the left side of the predicate
- ///
- /// The path pointing to the left side value inside the list
- 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();
- }
-
- ///
- /// Updates the right side of the predicate using a path and makes the predicate dynamic
- ///
- /// The path pointing to the right side value
- 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;
- }
-
- ///
- /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
- ///
- /// The right side value to use
- public void UpdateRightSideStatic(object? staticValue)
- {
- PredicateType = ProfileRightSideType.Static;
- RightPath?.Dispose();
- RightPath = null;
-
- SetStaticValue(staticValue);
- }
-
- ///
- /// Updates the operator of the predicate and re-compiles the expression
- ///
- ///
- 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
+
+ ///
+ 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
+
///
/// Not supported for list predicates, always returns false
///
@@ -158,45 +121,6 @@ namespace Artemis.Core
return false;
}
- ///
- /// Determines whether the provided path is contained inside the list
- ///
- /// The path to evaluate
- 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
-
- ///
- 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
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs
new file mode 100644
index 000000000..aee8d2adc
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs
@@ -0,0 +1,37 @@
+using System;
+using Artemis.Core.DataModelExpansions;
+
+namespace Artemis.Core
+{
+ internal class EventPredicateWrapperDataModel : EventPredicateWrapperDataModel
+ {
+ [DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")]
+ public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!;
+ }
+
+ ///
+ /// Represents a datamodel that wraps the event arguments of an event
+ ///
+ public abstract class EventPredicateWrapperDataModel : DataModel
+ {
+ internal EventPredicateWrapperDataModel()
+ {
+ PluginInfo = Constants.CorePluginInfo;
+ }
+
+ [DataModelIgnore]
+ public object? UntypedArguments { get; set; }
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ 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 for type {type.Name}");
+
+ return (EventPredicateWrapperDataModel) instance;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs
similarity index 81%
rename from src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs
rename to src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs
index 7c436e8aa..1c8a3d458 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs
@@ -9,6 +9,9 @@ namespace Artemis.Core
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
}
+ ///
+ /// Represents a datamodel that wraps a value in a list
+ ///
public abstract class ListPredicateWrapperDataModel : DataModel
{
internal ListPredicateWrapperDataModel()
@@ -19,6 +22,9 @@ namespace Artemis.Core
[DataModelIgnore]
public object? UntypedValue { get; set; }
+ ///
+ /// Creates a new instance of the class
+ ///
public static ListPredicateWrapperDataModel Create(Type type)
{
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
index c1e86d9cf..f62920e14 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
@@ -60,9 +60,9 @@ namespace Artemis.Core
/// Gets ors ets the easing function of the data binding
///
public Easings.Functions EasingFunction { get; set; }
-
+
internal DataBindingEntity Entity { get; }
-
+
///
/// Gets the current value of the data binding
///
@@ -137,16 +137,26 @@ namespace Artemis.Core
///
/// Updates the smoothing progress of the data binding
///
- /// The time in seconds that passed since the last update
- public void Update(double deltaTime)
+ /// The timeline to apply during update
+ public void Update(Timeline timeline)
+ {
+ UpdateWithDelta(timeline.Delta);
+ }
+
+ ///
+ /// Updates the smoothing progress of the data binding
+ ///
+ /// The delta to apply during update
+ 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;
}
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs
index 7b4e26a5b..765e0927c 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs
@@ -75,7 +75,7 @@ namespace Artemis.Core
return condition;
}
-
+
///
/// Removes a condition from the conditional data binding's collection and disposes it
///
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs
index 05d4c0aba..10732dfd3 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs
@@ -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();
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
new file mode 100644
index 000000000..c9e2c32d8
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.Core.DataModelExpansions;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a data model event with event arguments of type
+ ///
+ public class DataModelEvent : IDataModelEvent where T : DataModelEventArgs
+ {
+ private bool _trackHistory;
+
+ ///
+ /// Creates a new instance of the class with history tracking disabled
+ ///
+ public DataModelEvent()
+ {
+ }
+
+ ///
+ /// Creates a new instance of the
+ ///
+ /// A boolean indicating whether the last 20 events should be tracked
+ public DataModelEvent(bool trackHistory)
+ {
+ _trackHistory = trackHistory;
+ }
+
+ ///
+ [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
+ public DateTime LastTrigger { get; private set; }
+
+ ///
+ /// Gets the event arguments of the last time the event was triggered
+ ///
+ [DataModelProperty(Description = "The arguments of the last time this event triggered")]
+ public T? LastEventArguments { get; private set; }
+
+ ///
+ [DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
+ public int TriggerCount { get; private set; }
+
+ ///
+ /// Gets a queue of the last 20 event arguments
+ /// Always empty if is
+ ///
+ [DataModelProperty(Description = "The arguments of the last time this event triggered")]
+ public Queue EventArgumentsHistory { get; } = new Queue(20);
+
+ ///
+ /// Trigger the event with the given
+ ///
+ /// The event argument to pass to the event
+ 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);
+ }
+
+ ///
+ [DataModelIgnore]
+ public Type ArgumentsType => typeof(T);
+
+ ///
+ [DataModelIgnore]
+ public bool TrackHistory
+ {
+ get => _trackHistory;
+ set
+ {
+ EventArgumentsHistory.Clear();
+ _trackHistory = value;
+ }
+ }
+
+ ///
+ [DataModelIgnore]
+ public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
+
+ ///
+ [DataModelIgnore]
+ public List EventArgumentsHistoryUntyped => EventArgumentsHistory.Cast().ToList();
+
+ ///
+ public event EventHandler? EventTriggered;
+
+ ///
+ public void Reset()
+ {
+ TriggerCount = 0;
+ EventArgumentsHistory.Clear();
+ }
+ }
+
+ ///
+ /// Represents a data model event without event arguments
+ ///
+ public class DataModelEvent : IDataModelEvent
+ {
+ private bool _trackHistory;
+
+ ///
+ /// Creates a new instance of the class with history tracking disabled
+ ///
+ public DataModelEvent()
+ {
+ }
+
+ ///
+ /// Creates a new instance of the
+ ///
+ /// A boolean indicating whether the last 20 events should be tracked
+ public DataModelEvent(bool trackHistory)
+ {
+ _trackHistory = trackHistory;
+ }
+
+ ///
+ [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
+ public DateTime LastTrigger { get; private set; }
+
+ ///
+ /// Gets the event arguments of the last time the event was triggered
+ ///
+ [DataModelProperty(Description = "The arguments of the last time this event triggered")]
+ public DataModelEventArgs? LastEventArguments { get; private set; }
+
+ ///
+ [DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
+ public int TriggerCount { get; private set; }
+
+ ///
+ /// Gets a queue of the last 20 event arguments
+ /// Always empty if is
+ ///
+ [DataModelProperty(Description = "The arguments of the last time this event triggered")]
+ public Queue EventArgumentsHistory { get; } = new Queue(20);
+
+ ///
+ /// Trigger the event
+ ///
+ 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);
+ }
+
+ ///
+ [DataModelIgnore]
+ public Type ArgumentsType => typeof(DataModelEventArgs);
+
+ ///
+ [DataModelIgnore]
+ public bool TrackHistory
+ {
+ get => _trackHistory;
+ set
+ {
+ EventArgumentsHistory.Clear();
+ _trackHistory = value;
+ }
+ }
+
+ ///
+ [DataModelIgnore]
+ public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
+
+ ///
+ [DataModelIgnore]
+ public List EventArgumentsHistoryUntyped => EventArgumentsHistory.ToList();
+
+ ///
+ public event EventHandler? EventTriggered;
+
+ ///
+ public void Reset()
+ {
+ TriggerCount = 0;
+ EventArgumentsHistory.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs
new file mode 100644
index 000000000..3e20a9b1c
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents the base class for data model events that contain event data
+ ///
+ public class DataModelEventArgs
+ {
+ ///
+ /// Gets the time at which the event with these arguments was triggered
+ ///
+ public DateTime TriggerTime { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
index 64f62ed68..054257402 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
@@ -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
///
public IReadOnlyCollection Segments => _segments.ToList().AsReadOnly();
- ///
- /// Gets a boolean indicating whether this data model path points to a list
- ///
- public bool PointsToList
- {
- get
- {
- Type? type = GetPropertyType();
- return type?.IsGenericEnumerable() ?? false;
- }
- }
-
internal DataModelPathEntity Entity { get; }
internal Func
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 leds = new List();
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
index aaa36796b..512b63213 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
@@ -11,7 +11,7 @@ namespace Artemis.Core
/// initialize these for you.
///
///
- public interface ILayerProperty : IStorageModel, IUpdateModel, IDisposable
+ public interface ILayerProperty : IStorageModel, IDisposable
{
///
/// Gets the description attribute applied to this property
@@ -36,5 +36,11 @@ namespace Artemis.Core
/// Returns a list off all data binding registrations
///
List GetAllDataBindingRegistrations();
+
+ ///
+ /// Updates the layer properties internal state
+ ///
+ /// The timeline to apply to the property
+ void Update(Timeline timeline);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 910ca1324..39a18a503 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -34,20 +34,16 @@ namespace Artemis.Core
///
public string Path { get; private set; }
- ///
- /// Updates the property, applying keyframes and data bindings to the current value
- ///
- public void Update(double deltaTime)
+ ///
+ 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();
}
///
@@ -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(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);
}
///
@@ -485,12 +489,10 @@ namespace Artemis.Core
_keyframes.Clear();
try
{
- _keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe(
- JsonConvert.DeserializeObject(k.Value),
- k.Position,
- (Easings.Functions) k.EasingFunction,
- this
- )));
+ _keyframes.AddRange(
+ Entity.KeyframeEntities.Where(k => k.Position <= ProfileElement.Timeline.Length)
+ .Select(k => new LayerPropertyKeyframe(JsonConvert.DeserializeObject(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this))
+ );
}
catch (JsonException e)
{
diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
index c127199ef..48afecab9 100644
--- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
+++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
@@ -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 PropertyGroupUpdating;
-
///
/// Occurs when the property group has initialized all its children
///
@@ -284,11 +283,6 @@ namespace Artemis.Core
///
public event EventHandler VisibilityChanged;
- internal virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e)
- {
- PropertyGroupUpdating?.Invoke(this, e);
- }
-
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index cc224c313..786ed8369 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -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);
}
}
+ ///
+ 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())
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 836d8c2e3..56b86b240 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -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 ChildrenList;
protected ProfileElement()
@@ -91,7 +91,12 @@ namespace Artemis.Core
///
/// Renders the element
///
- public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
+ public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
+
+ ///
+ /// Resets the internal state of the element
+ ///
+ public abstract void Reset();
///
public override string ToString()
@@ -108,9 +113,9 @@ namespace Artemis.Core
/// The order where to place the child (1-based), defaults to the end of the collection
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
/// The profile element to remove
public virtual void RemoveChild(ProfileElement child)
{
- if (_disposed)
+ if (Disposed)
throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList)
@@ -175,7 +180,7 @@ namespace Artemis.Core
///
public List GetAllFolders()
{
- if (_disposed)
+ if (Disposed)
throw new ObjectDisposedException(GetType().Name);
List folders = new List();
@@ -196,7 +201,7 @@ namespace Artemis.Core
///
public List GetAllLayers()
{
- if (_disposed)
+ if (Disposed)
throw new ObjectDisposedException(GetType().Name);
List layers = new List();
diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
index a30f31c17..27fe3246d 100644
--- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
@@ -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 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
+
+ ///
+ /// Gets the timeline associated with this render element
+ ///
+ public Timeline Timeline { get; private set; }
+
+ ///
+ /// Updates the according to the provided and current display condition status
+ ///
+ 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;
-
- ///
- /// Gets or sets the length of the start segment
- ///
- public TimeSpan StartSegmentLength
- {
- get => _startSegmentLength;
- set
- {
- if (!SetAndNotify(ref _startSegmentLength, value)) return;
- UpdateTimelineLength();
- if (Parent is RenderProfileElement renderElement)
- renderElement.UpdateTimelineLength();
- }
- }
-
- ///
- /// Gets or sets the length of the main segment
- ///
- public TimeSpan MainSegmentLength
- {
- get => _mainSegmentLength;
- set
- {
- if (!SetAndNotify(ref _mainSegmentLength, value)) return;
- UpdateTimelineLength();
- if (Parent is RenderProfileElement renderElement)
- renderElement.UpdateTimelineLength();
- }
- }
-
- ///
- /// Gets or sets the length of the end segment
- ///
- public TimeSpan EndSegmentLength
- {
- get => _endSegmentLength;
- set
- {
- if (!SetAndNotify(ref _endSegmentLength, value)) return;
- UpdateTimelineLength();
- if (Parent is RenderProfileElement renderElement)
- renderElement.UpdateTimelineLength();
- }
- }
-
- ///
- /// Gets the current timeline position
- ///
- public TimeSpan TimelinePosition
- {
- get => _timelinePosition;
- protected set => SetAndNotify(ref _timelinePosition, value);
- }
-
- ///
- /// Gets or sets whether main timeline should repeat itself as long as display conditions are met
- ///
- public bool DisplayContinuously
- {
- get => _displayContinuously;
- set => SetAndNotify(ref _displayContinuously, value);
- }
-
- ///
- /// Gets or sets whether the timeline should finish when conditions are no longer met
- ///
- public bool AlwaysFinishTimeline
- {
- get => _alwaysFinishTimeline;
- set => SetAndNotify(ref _alwaysFinishTimeline, value);
- }
-
- ///
- /// Gets the max length of this element and any of its children
- ///
- 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();
-
- ///
- /// Overrides the progress of the element
- ///
- ///
- ///
- public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
-
- #endregion
-
#region Effect management
protected List _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;
///
@@ -409,20 +299,46 @@ namespace Artemis.Core
}
///
- /// 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
///
- public bool ApplyKeyframesEnabled { get; set; }
-
- ///
- /// Gets or sets whether data bindings should be applied when this profile element updates
- ///
- 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;
}
diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs
new file mode 100644
index 000000000..6397b34c9
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Timeline.cs
@@ -0,0 +1,493 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Artemis.Storage.Entities.Profile;
+using Stylet;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a timeline used by profile elements
+ ///
+ public class Timeline : PropertyChangedBase, IStorageModel
+ {
+ ///
+ /// Creates a new instance of the class
+ ///
+ public Timeline()
+ {
+ Entity = new TimelineEntity();
+ _extraTimelines = new List();
+ MainSegmentLength = TimeSpan.FromSeconds(5);
+
+ Save();
+ }
+
+ internal Timeline(TimelineEntity entity)
+ {
+ Entity = entity;
+ _extraTimelines = new List();
+
+ Load();
+ }
+
+ private Timeline(Timeline parent)
+ {
+ Parent = parent;
+ }
+
+ #region Extra timelines
+
+ ///
+ /// Adds an extra timeline to this timeline
+ ///
+ public void AddExtraTimeline()
+ {
+ _extraTimelines.Add(new Timeline(this));
+ }
+
+ ///
+ /// Removes all extra timelines from this timeline
+ ///
+ 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 _extraTimelines;
+ private TimeSpan _startSegmentLength;
+ private TimeSpan _mainSegmentLength;
+ private TimeSpan _endSegmentLength;
+
+ ///
+ /// Gets the parent this timeline is an extra timeline of
+ ///
+ public Timeline? Parent { get; }
+
+ ///
+ /// Gets the current position of the timeline
+ ///
+ public TimeSpan Position
+ {
+ get => _position;
+ private set => SetAndNotify(ref _position, value);
+ }
+
+ ///
+ /// Gets the cumulative delta of all calls to that took place after the last call to
+ ///
+ /// Note: If this is an extra timeline is always equal to
+ ///
+ ///
+ public TimeSpan Delta
+ {
+ get => Parent == null ? _lastDelta : DeltaToParent;
+ private set => SetAndNotify(ref _lastDelta, value);
+ }
+
+ ///
+ /// Gets the delta to this timeline's
+ ///
+ public TimeSpan DeltaToParent => Parent != null ? Position - Parent.Position : TimeSpan.Zero;
+
+ ///
+ /// Gets or sets the mode in which the render element starts its timeline when display conditions are met
+ ///
+ public TimelinePlayMode PlayMode
+ {
+ get => _playMode;
+ set => SetAndNotify(ref _playMode, value);
+ }
+
+ ///
+ /// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
+ ///
+ public TimelineStopMode StopMode
+ {
+ get => _stopMode;
+ set => SetAndNotify(ref _stopMode, value);
+ }
+
+ ///
+ /// Gets or sets the mode in which the render element responds to display condition events being fired before the
+ /// timeline finished
+ ///
+ public TimeLineEventOverlapMode EventOverlapMode
+ {
+ get => _eventOverlapMode;
+ set => SetAndNotify(ref _eventOverlapMode, value);
+ }
+
+ ///
+ /// Gets a list of extra copies of the timeline applied to this timeline
+ ///
+ public ReadOnlyCollection ExtraTimelines => _extraTimelines.AsReadOnly();
+
+ ///
+ /// Gets a boolean indicating whether the timeline has finished its run
+ ///
+ public bool IsFinished => Position > Length || Length == TimeSpan.Zero;
+
+ ///
+ /// Gets a boolean indicating whether the timeline progress has been overridden
+ ///
+ public bool IsOverridden { get; private set; }
+
+ #region Segments
+
+ ///
+ /// Gets the total length of this timeline
+ ///
+ public TimeSpan Length => StartSegmentLength + MainSegmentLength + EndSegmentLength;
+
+ ///
+ /// Gets or sets the length of the start segment
+ ///
+ public TimeSpan StartSegmentLength
+ {
+ get => _startSegmentLength;
+ set
+ {
+ if (SetAndNotify(ref _startSegmentLength, value))
+ NotifySegmentShiftAt(TimelineSegment.Start, false);
+ }
+ }
+
+ ///
+ /// Gets or sets the length of the main segment
+ ///
+ public TimeSpan MainSegmentLength
+ {
+ get => _mainSegmentLength;
+ set
+ {
+ if (SetAndNotify(ref _mainSegmentLength, value))
+ NotifySegmentShiftAt(TimelineSegment.Main, false);
+ }
+ }
+
+ ///
+ /// Gets or sets the length of the end segment
+ ///
+ public TimeSpan EndSegmentLength
+ {
+ get => _endSegmentLength;
+ set
+ {
+ if (SetAndNotify(ref _endSegmentLength, value))
+ NotifySegmentShiftAt(TimelineSegment.End, false);
+ }
+ }
+
+ ///
+ /// Gets or sets the start position of the main segment
+ ///
+ public TimeSpan MainSegmentStartPosition
+ {
+ get => StartSegmentEndPosition;
+ set
+ {
+ StartSegmentEndPosition = value;
+ NotifySegmentShiftAt(TimelineSegment.Main, true);
+ }
+ }
+
+ ///
+ /// Gets or sets the end position of the end segment
+ ///
+ public TimeSpan EndSegmentStartPosition
+ {
+ get => MainSegmentEndPosition;
+ set
+ {
+ MainSegmentEndPosition = value;
+ NotifySegmentShiftAt(TimelineSegment.End, true);
+ }
+ }
+
+ ///
+ /// Gets or sets the end position of the start segment
+ ///
+ public TimeSpan StartSegmentEndPosition
+ {
+ get => StartSegmentLength;
+ set
+ {
+ StartSegmentLength = value;
+ NotifySegmentShiftAt(TimelineSegment.Start, false);
+ }
+ }
+
+ ///
+ /// Gets or sets the end position of the main segment
+ ///
+ public TimeSpan MainSegmentEndPosition
+ {
+ get => StartSegmentEndPosition + MainSegmentLength;
+ set
+ {
+ MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero;
+ NotifySegmentShiftAt(TimelineSegment.Main, false);
+ }
+ }
+
+ ///
+ /// Gets or sets the end position of the end segment
+ ///
+ 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; }
+
+ ///
+ /// Notifies the right segments in a way that I don't have to think about it
+ ///
+ /// The segment that was updated
+ /// Whether the start point of the was updated
+ 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
+
+ ///
+ /// Updates the timeline, applying the provided to the
+ ///
+ /// The amount of time to apply to the position
+ /// Whether to stick to the main segment, wrapping around if needed
+ 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);
+ }
+ }
+
+ ///
+ /// Moves the position of the timeline backwards to the very start of the timeline
+ ///
+ public void JumpToStart()
+ {
+ lock (this)
+ {
+ if (Position == TimeSpan.Zero)
+ return;
+
+ Delta = TimeSpan.Zero - Position;
+ Position = TimeSpan.Zero;
+
+ _extraTimelines.Clear();
+ }
+ }
+
+ ///
+ /// Moves the position of the timeline forwards to the beginning of the end segment
+ ///
+ public void JumpToEndSegment()
+ {
+ lock (this)
+ {
+ if (Position >= EndSegmentStartPosition)
+ return;
+
+ Delta = EndSegmentStartPosition - Position;
+ Position = EndSegmentStartPosition;
+
+ _extraTimelines.Clear();
+ }
+ }
+
+ ///
+ /// Moves the position of the timeline forwards to the very end of the timeline
+ ///
+ public void JumpToEnd()
+ {
+ lock (this)
+ {
+ if (Position >= EndSegmentEndPosition)
+ return;
+
+ Delta = EndSegmentEndPosition - Position;
+ Position = EndSegmentEndPosition;
+
+ _extraTimelines.Clear();
+ }
+ }
+
+ ///
+ /// Overrides the to the specified time and clears any extra time lines
+ ///
+ /// The position to set the timeline to
+ /// Whether to stick to the main segment, wrapping around if needed
+ 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();
+ }
+ }
+
+ ///
+ /// Sets the to
+ ///
+ public void ClearDelta()
+ {
+ lock (this)
+ {
+ Delta = TimeSpan.Zero;
+ }
+ }
+
+ #endregion
+
+ #region Storage
+
+ ///
+ public void Load()
+ {
+ StartSegmentLength = Entity.StartSegmentLength;
+ MainSegmentLength = Entity.MainSegmentLength;
+ EndSegmentLength = Entity.EndSegmentLength;
+ PlayMode = (TimelinePlayMode) Entity.PlayMode;
+ StopMode = (TimelineStopMode) Entity.StopMode;
+ EventOverlapMode = (TimeLineEventOverlapMode) Entity.EventOverlapMode;
+ }
+
+ ///
+ 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
+ }
+
+ ///
+ /// Represents a mode for render elements to start their timeline when display conditions are met
+ ///
+ public enum TimelinePlayMode
+ {
+ ///
+ /// Continue repeating the main segment of the timeline while the condition is met
+ ///
+ Repeat,
+
+ ///
+ /// Only play the timeline once when the condition is met
+ ///
+ Once
+ }
+
+ ///
+ /// Represents a mode for render elements to stop their timeline when display conditions are no longer met
+ ///
+ public enum TimelineStopMode
+ {
+ ///
+ /// When conditions are no longer met, finish the the current run of the main timeline
+ ///
+ Finish,
+
+ ///
+ /// When conditions are no longer met, skip to the end segment of the timeline
+ ///
+ SkipToEnd
+ }
+
+ ///
+ /// Represents a mode for render elements to start their timeline when display conditions events are fired
+ ///
+ public enum TimeLineEventOverlapMode
+ {
+ ///
+ /// Stop the current run and restart the timeline
+ ///
+ Restart,
+
+ ///
+ /// Ignore subsequent event fires until the timeline finishes
+ ///
+ Ignore,
+
+ ///
+ /// Play another copy of the timeline on top of the current run
+ ///
+ Copy
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
index 8f1930609..d72015308 100644
--- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
+++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
@@ -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
///
/// Gets the currently active profile
///
- public Profile ActiveProfile { get; private set; }
+ public Profile? ActiveProfile { get; private set; }
///
/// 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);
diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index d3ba8b262..5abb34c91 100644
--- a/src/Artemis.Core/Plugins/Plugin.cs
+++ b/src/Artemis.Core/Plugins/Plugin.cs
@@ -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
///
- /// The resulting plugin update registration
+ /// The resulting plugin update registration which can be used to stop the update
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action action)
{
if (action == null)
diff --git a/src/Artemis.Core/Resources/intro-profile.json b/src/Artemis.Core/Resources/intro-profile.json
index abd185a69..972e89c7c 100644
--- a/src/Artemis.Core/Resources/intro-profile.json
+++ b/src/Artemis.Core/Resources/intro-profile.json
@@ -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
}
}
]
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 824649181..7901cb0a0 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -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 {new SKColorConverter()}
+ Converters = new List {new SKColorConverter(), new ForgivingIntConverter()}
};
}
diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs
index fa328b720..ec583c2d8 100644
--- a/src/Artemis.Core/Services/PluginService.cs
+++ b/src/Artemis.Core/Services/PluginService.cs
@@ -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);
diff --git a/src/Artemis.Core/Services/Registration/DataBindingService.cs b/src/Artemis.Core/Services/Registration/DataBindingService.cs
index 28218cf65..96dedfe50 100644
--- a/src/Artemis.Core/Services/Registration/DataBindingService.cs
+++ b/src/Artemis.Core/Services/Registration/DataBindingService.cs
@@ -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());
diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
index ac2bd2d04..d7a1d9787 100644
--- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
@@ -49,6 +49,11 @@ namespace Artemis.Core.Services
///
void ActivateLastProfile(ProfileModule profileModule);
+ ///
+ /// Reloads the currently active profile on the provided profile module
+ ///
+ void ReloadProfile(ProfileModule module);
+
///
/// Asynchronously activates the last profile of the given profile module using a fade animation
///
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index 32512f627..880ff1ab1 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -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 ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
{
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
diff --git a/src/Artemis.Core/Utilities/DeserializationLogger.cs b/src/Artemis.Core/Utilities/DeserializationLogger.cs
index e1c176283..de4424def 100644
--- a/src/Artemis.Core/Utilities/DeserializationLogger.cs
+++ b/src/Artemis.Core/Utilities/DeserializationLogger.cs
@@ -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);
diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs
index 63008a8dc..3e4b90eb8 100644
--- a/src/Artemis.Core/Utilities/IntroAnimation.cs
+++ b/src/Artemis.Core/Utilities/IntroAnimation.cs
@@ -32,7 +32,7 @@ namespace Artemis.Core
return;
AnimationProfile.Update(deltaTime);
- AnimationProfile.Render(deltaTime, canvas, bitmapInfo);
+ AnimationProfile.Render(canvas, bitmapInfo);
}
private void CreateIntroProfile()
diff --git a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs
index 7e7556e36..d8f7ded16 100644
--- a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs
@@ -6,16 +6,11 @@ namespace Artemis.Storage.Entities.Profile.Abstract
{
public abstract class RenderElementEntity
{
- public TimeSpan StartSegmentLength { get; set; }
- public TimeSpan MainSegmentLength { get; set; }
- public TimeSpan EndSegmentLength { get; set; }
- public bool DisplayContinuously { get; set; }
- public bool AlwaysFinishTimeline { get; set; }
-
public List LayerEffects { get; set; }
public List PropertyEntities { get; set; }
public List ExpandedPropertyGroups { get; set; }
public DataModelConditionGroupEntity DisplayCondition { get; set; }
+ public TimelineEntity Timeline { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs
new file mode 100644
index 000000000..141f5bb08
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using Artemis.Storage.Entities.Profile.Abstract;
+
+namespace Artemis.Storage.Entities.Profile.Conditions
+{
+ public class DataModelConditionEventEntity : DataModelConditionPartEntity
+ {
+ public DataModelConditionEventEntity()
+ {
+ Children = new List();
+ }
+
+ public DataModelPathEntity EventPath { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs
new file mode 100644
index 000000000..fee134489
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs
@@ -0,0 +1,6 @@
+namespace Artemis.Storage.Entities.Profile.Conditions
+{
+ public class DataModelConditionEventPredicateEntity : DataModelConditionPredicateEntity
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs
new file mode 100644
index 000000000..1a0fa93ea
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs
@@ -0,0 +1,6 @@
+namespace Artemis.Storage.Entities.Profile.Conditions
+{
+ public class DataModelConditionGeneralPredicateEntity : DataModelConditionPredicateEntity
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs
index 8c8959ab4..fd812cc41 100644
--- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs
index 513a9af43..cfc4b5372 100644
--- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs
@@ -1,20 +1,6 @@
-using System;
-using Artemis.Storage.Entities.Profile.Abstract;
-
-namespace Artemis.Storage.Entities.Profile.Conditions
+namespace Artemis.Storage.Entities.Profile.Conditions
{
- public class DataModelConditionListPredicateEntity : DataModelConditionPartEntity
+ public class DataModelConditionListPredicateEntity : DataModelConditionPredicateEntity
{
- public int PredicateType { get; set; }
-
- public DataModelPathEntity LeftPath { get; set; }
- public DataModelPathEntity RightPath { get; set; }
-
- // Stored as a string to be able to control serialization and deserialization ourselves
- public string RightStaticValue { get; set; }
-
- public string OperatorType { get; set; }
- public Guid? OperatorPluginGuid { get; set; }
-
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs
index 08ee41d25..c913a6a3c 100644
--- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs
@@ -3,7 +3,7 @@ using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
{
- public class DataModelConditionPredicateEntity : DataModelConditionPartEntity
+ public abstract class DataModelConditionPredicateEntity : DataModelConditionPartEntity
{
public int PredicateType { get; set; }
public DataModelPathEntity LeftPath { get; set; }
@@ -14,6 +14,5 @@ namespace Artemis.Storage.Entities.Profile.Conditions
// Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; }
-
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs
index b1e9a3241..b4a2b03b5 100644
--- a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs
@@ -6,5 +6,14 @@ namespace Artemis.Storage.Entities.Profile
{
public string Path { get; set; }
public Guid? DataModelGuid { get; set; }
+
+ public PathWrapperType WrapperType { get; set; }
+ }
+
+ public enum PathWrapperType
+ {
+ None,
+ List,
+ Event
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs b/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs
new file mode 100644
index 000000000..6d17188c6
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Artemis.Storage.Entities.Profile
+{
+ public class TimelineEntity
+ {
+ public TimeSpan StartSegmentLength { get; set; }
+ public TimeSpan MainSegmentLength { get; set; }
+ public TimeSpan EndSegmentLength { get; set; }
+
+ public int PlayMode { get; set; }
+ public int StopMode { get; set; }
+ public int EventOverlapMode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs
index d5c85542e..c54e3dc0f 100644
--- a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs
+++ b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs
@@ -13,31 +13,32 @@ namespace Artemis.Storage.Migrations
public void Apply(LiteRepository repository)
{
- List profiles = repository.Query().ToList();
- foreach (ProfileEntity profileEntity in profiles)
- {
- foreach (FolderEntity folder in profileEntity.Folders.Where(f => f.MainSegmentLength == TimeSpan.Zero))
- {
- if (folder.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
- folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
- if (folder.MainSegmentLength == TimeSpan.Zero)
- folder.MainSegmentLength = TimeSpan.FromSeconds(5);
-
- folder.DisplayContinuously = true;
- }
-
- foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero))
- {
- if (layer.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
- layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
- if (layer.MainSegmentLength == TimeSpan.Zero)
- layer.MainSegmentLength = TimeSpan.FromSeconds(5);
-
- layer.DisplayContinuously = true;
- }
-
- repository.Update(profileEntity);
- }
+ // Lesson for next time: Use BsonDocuments in migrations
+ // List profiles = repository.Query().ToList();
+ // foreach (ProfileEntity profileEntity in profiles)
+ // {
+ // foreach (FolderEntity folder in profileEntity.Folders.Where(f => f.MainSegmentLength == TimeSpan.Zero))
+ // {
+ // if (folder.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
+ // folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
+ // if (folder.MainSegmentLength == TimeSpan.Zero)
+ // folder.MainSegmentLength = TimeSpan.FromSeconds(5);
+ //
+ // folder.PlayMode = 0;
+ // }
+ //
+ // foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero))
+ // {
+ // if (layer.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
+ // layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
+ // if (layer.MainSegmentLength == TimeSpan.Zero)
+ // layer.MainSegmentLength = TimeSpan.FromSeconds(5);
+ //
+ // layer.PlayMode = 0;
+ // }
+ //
+ // repository.Update(profileEntity);
+ // }
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs b/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs
new file mode 100644
index 000000000..339b42358
--- /dev/null
+++ b/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs
@@ -0,0 +1,49 @@
+using System;
+using Artemis.Storage.Migrations.Interfaces;
+using LiteDB;
+
+namespace Artemis.Storage.Migrations
+{
+ public class M6PredicateAbstraction : IStorageMigration
+ {
+ public int UserVersion => 6;
+
+ public void Apply(LiteRepository repository)
+ {
+ ILiteCollection collection = repository.Database.GetCollection("ProfileEntity");
+ foreach (BsonDocument bsonDocument in collection.FindAll())
+ {
+ foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray)
+ Migrate(bsonLayer);
+
+ foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray)
+ Migrate(bsonLayer);
+
+ collection.Update(bsonDocument);
+ }
+ }
+
+ private void Migrate(BsonValue bsonValue)
+ {
+ if (bsonValue.IsArray)
+ {
+ foreach (BsonValue child in bsonValue.AsArray)
+ Migrate(child);
+ return;
+ }
+
+ if (bsonValue.IsDocument)
+ {
+ // See if the document has a type
+ if (bsonValue.AsDocument.TryGetValue("_type", out BsonValue typeValue))
+ {
+ if (typeValue.AsString == "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionPredicateEntity, Artemis.Storage")
+ bsonValue.AsDocument["_type"] = "Artemis.Storage.Entities.Profile.Conditions.DataModelConditionGeneralPredicateEntity, Artemis.Storage";
+ }
+
+ foreach (BsonValue documentValue in bsonValue.AsDocument.Values)
+ Migrate(documentValue);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
index 6cbf8796d..1292ea906 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
@@ -1,2 +1,15 @@
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs
index a4502e62f..88f1a0933 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs
@@ -97,7 +97,11 @@ namespace Artemis.UI.Shared.Input
public bool IsDataModelViewModelOpen
{
get => _isDataModelViewModelOpen;
- set => SetAndNotify(ref _isDataModelViewModelOpen, value);
+ set
+ {
+ if (!SetAndNotify(ref _isDataModelViewModelOpen, value)) return;
+ if (value) UpdateDataModelVisualization();
+ }
}
public DataModelPath DataModelPath
@@ -127,6 +131,8 @@ namespace Artemis.UI.Shared.Input
}
}
+ public bool LoadEventChildren { get; set; } = true;
+
public void ChangeDataModel(DataModelPropertiesViewModel dataModel)
{
if (DataModelViewModel != null)
@@ -197,10 +203,15 @@ namespace Artemis.UI.Shared.Input
{
if (!IsDataModelViewModelOpen)
return;
+
+ UpdateDataModelVisualization();
+ }
- DataModelViewModel.Update(_dataModelUIService);
+ private void UpdateDataModelVisualization()
+ {
+ DataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren));
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
- extraDataModelViewModel.Update(_dataModelUIService);
+ extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren));
}
#endregion
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml
index 3caf803cf..a069d383a 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml
@@ -16,12 +16,21 @@
+
-
-
+
-
+
+
+
+
+
+
@@ -73,6 +79,12 @@
+
+
+
+
+
+
diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs
index e5b440c9c..9f164e23d 100644
--- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs
+++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs
@@ -37,8 +37,8 @@ namespace Artemis.UI.Shared.Services
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
// Update to populate children
- viewModel.Update(this);
- viewModel.UpdateRequested += (sender, args) => viewModel.Update(this);
+ viewModel.Update(this, null);
+ viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
return viewModel;
}
@@ -67,8 +67,8 @@ namespace Artemis.UI.Shared.Services
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, null));
// Update to populate children
- viewModel.Update(this);
- viewModel.UpdateRequested += (sender, args) => viewModel.Update(this);
+ viewModel.Update(this, null);
+ viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
return viewModel;
}
@@ -173,7 +173,7 @@ namespace Artemis.UI.Shared.Services
else
result = _kernel.Get();
- if (result != null)
+ if (result != null)
result.PropertyDescription = description;
return result;
@@ -220,8 +220,7 @@ namespace Artemis.UI.Shared.Services
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
- IParameter[] parameters = new IParameter[]
- {
+ IParameter[] parameters = {
new ConstructorArgument("targetDescription", description),
new ConstructorArgument("initialValue", initialValue)
};
diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs
index bf9403174..08ab28760 100644
--- a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs
+++ b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs
@@ -57,8 +57,7 @@ namespace Artemis.UI.Shared.Services
public async Task ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
{
- IParameter[] arguments =
- {
+ IParameter[] arguments = {
new ConstructorArgument("header", header),
new ConstructorArgument("text", text),
new ConstructorArgument("confirmText", confirmText.ToUpper()),
@@ -70,8 +69,7 @@ namespace Artemis.UI.Shared.Services
public async Task ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
{
- IParameter[] arguments =
- {
+ IParameter[] arguments = {
new ConstructorArgument("header", header),
new ConstructorArgument("text", text),
new ConstructorArgument("confirmText", confirmText.ToUpper()),
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index 8afc4f4c0..f50b80185 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -15,6 +15,7 @@ namespace Artemis.UI.Shared.Services
internal class ProfileEditorService : IProfileEditorService
{
private readonly ILogger _logger;
+ private readonly ICoreService _coreService;
private readonly IProfileService _profileService;
private readonly List _registeredPropertyEditors;
private readonly object _selectedProfileElementLock = new object();
@@ -22,16 +23,23 @@ namespace Artemis.UI.Shared.Services
private TimeSpan _currentTime;
private int _pixelsPerSecond;
- public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger)
+ public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService)
{
_profileService = profileService;
_logger = logger;
+ _coreService = coreService;
_registeredPropertyEditors = new List();
Kernel = kernel;
PixelsPerSecond = 100;
}
+ private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
+ {
+ _coreService.FrameRendered -= CoreServiceOnFrameRendered;
+ Execute.PostToUIThread(OnProfilePreviewUpdated);
+ }
+
public IKernel Kernel { get; }
public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
public Profile SelectedProfile { get; private set; }
@@ -44,8 +52,8 @@ namespace Artemis.UI.Shared.Services
set
{
if (_currentTime.Equals(value)) return;
- if (SelectedProfileElement != null && value > SelectedProfileElement.TimelineLength)
- _currentTime = SelectedProfileElement.TimelineLength;
+ if (SelectedProfileElement != null && value > SelectedProfileElement.Timeline.Length)
+ _currentTime = SelectedProfileElement.Timeline.Length;
else
_currentTime = value;
UpdateProfilePreview();
@@ -142,11 +150,11 @@ namespace Artemis.UI.Shared.Services
// Stick to the main segment for any element that is not currently selected
foreach (Folder folder in SelectedProfile.GetAllFolders())
- folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement);
+ folder.Timeline.Override(CurrentTime, folder != SelectedProfileElement && folder.Timeline.PlayMode == TimelinePlayMode.Repeat);
foreach (Layer layer in SelectedProfile.GetAllLayers())
- layer.OverrideProgress(CurrentTime, layer != SelectedProfileElement);
+ layer.Timeline.Override(CurrentTime, layer != SelectedProfileElement && layer.Timeline.PlayMode == TimelinePlayMode.Repeat);
- OnProfilePreviewUpdated();
+ _coreService.FrameRendered += CoreServiceOnFrameRendered;
}
public bool UndoUpdateProfile()
@@ -224,24 +232,23 @@ namespace Artemis.UI.Shared.Services
if (snapToSegments)
{
// Snap to the end of the start segment
- if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.StartSegmentLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
- return SelectedProfileElement.StartSegmentLength;
+ if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.StartSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
+ return SelectedProfileElement.Timeline.StartSegmentEndPosition;
// Snap to the end of the main segment
- TimeSpan mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
- if (Math.Abs(time.TotalMilliseconds - mainSegmentEnd.TotalMilliseconds) < tolerance.TotalMilliseconds)
- return mainSegmentEnd;
+ if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.MainSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
+ return SelectedProfileElement.Timeline.MainSegmentEndPosition;
// Snap to the end of the end segment (end of the timeline)
- if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.TimelineLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
- return SelectedProfileElement.TimelineLength;
+ if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
+ return SelectedProfileElement.Timeline.EndSegmentEndPosition;
}
if (snapToCurrentTime)
{
// Snap to the current time
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
- return SelectedProfileElement.StartSegmentLength;
+ return CurrentTime;
}
if (snapTimes != null)
diff --git a/src/Artemis.UI/Artemis.UI.csproj.DotSettings b/src/Artemis.UI/Artemis.UI.csproj.DotSettings
new file mode 100644
index 000000000..7c2d2de36
--- /dev/null
+++ b/src/Artemis.UI/Artemis.UI.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/Artemis.UI/Converters/ComparisonConverter.cs b/src/Artemis.UI/Converters/ComparisonConverter.cs
new file mode 100644
index 000000000..6d3a1f196
--- /dev/null
+++ b/src/Artemis.UI/Converters/ComparisonConverter.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Artemis.UI.Converters
+{
+ public class ComparisonConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.Equals(parameter);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.Equals(true) == true ? parameter : Binding.DoNothing;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
index 8a2d0871a..28a691220 100644
--- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -65,10 +65,12 @@ namespace Artemis.UI.Ninject.Factories
public interface IDataModelConditionsVmFactory : IVmFactory
{
- DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, bool isListGroup);
+ DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType);
DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList);
- DataModelConditionPredicateViewModel DataModelConditionPredicateViewModel(DataModelConditionPredicate dataModelConditionPredicate);
+ DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent);
+ DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate);
DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate);
+ DataModelConditionEventPredicateViewModel DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate);
}
public interface ILayerPropertyVmFactory : IVmFactory
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs
similarity index 80%
rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs
rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs
index 622efce3c..5080adefc 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs
@@ -13,12 +13,11 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Conditions
{
- public class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable
+ public abstract class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable
{
private readonly IConditionOperatorService _conditionOperatorService;
private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService;
- private DataModelDynamicViewModel _leftSideSelectionViewModel;
private BindableCollection _operators;
private DataModelStaticViewModel _rightSideInputViewModel;
private DataModelDynamicViewModel _rightSideSelectionViewModel;
@@ -42,26 +41,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
Operators = new BindableCollection();
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues");
-
- Initialize();
}
public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model;
public PluginSetting ShowDataModelValues { get; }
-
- public BindableCollection Operators
- {
- get => _operators;
- set => SetAndNotify(ref _operators, value);
- }
-
- public DataModelDynamicViewModel LeftSideSelectionViewModel
- {
- get => _leftSideSelectionViewModel;
- set => SetAndNotify(ref _leftSideSelectionViewModel, value);
- }
-
public BaseConditionOperator SelectedOperator
{
get => _selectedOperator;
@@ -81,6 +65,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
}
public DelegateCommand SelectOperatorCommand { get; }
+ public BindableCollection Operators { get; }
+
+ protected SolidColorBrush LeftSideColor { get; set; }
public override void Delete()
{
@@ -88,15 +75,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
_profileEditorService.UpdateSelectedProfileElement();
}
- public void Initialize()
+ public virtual void Initialize()
{
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
+ if (LeftSideColor != null)
+ LeftSideSelectionViewModel.ButtonBrush = LeftSideColor;
+
// Determine which types are currently supported
- IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
- _supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
- _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
- _supportedInputTypes.Add(typeof(IEnumerable<>));
+ _supportedInputTypes = GetSupportedInputTypes();
+
Update();
}
@@ -105,13 +93,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath);
- Type leftSideType = LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
+ Type leftSideType = GetLeftSideType();
// Get the supported operators
Operators.Clear();
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
if (DataModelConditionPredicate.Operator == null)
- DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object), ConditionParameterSide.Left)));
+ DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault());
+ // The core doesn't care about best matches so if there is a new preferred operator, use that instead
+ else if (!Operators.Contains(DataModelConditionPredicate.Operator))
+ DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.Description == DataModelConditionPredicate.Operator.Description) ?? Operators.FirstOrDefault());
+
SelectedOperator = DataModelConditionPredicate.Operator;
// Without a selected operator or one that supports a right side, leave the right side input empty
@@ -136,25 +128,26 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
{
DisposeRightSideDynamicViewModel();
if (RightSideInputViewModel == null)
- CreateRightSideInputViewModel(SelectedOperator.RightSideType);
+ CreateRightSideInputViewModel();
- if (SelectedOperator.RightSideType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
- RightSideInputViewModel.Value = SelectedOperator.RightSideType.GetDefault();
+ Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
+ // Ensure the right static value is never null when the preferred type is a value type
+ if (preferredType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
+ RightSideInputViewModel.Value = preferredType.GetDefault();
else
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
- if (RightSideInputViewModel.TargetType != SelectedOperator.RightSideType)
- RightSideInputViewModel.UpdateTargetType(SelectedOperator.RightSideType);
+
+ if (RightSideInputViewModel.TargetType != preferredType)
+ RightSideInputViewModel.UpdateTargetType(preferredType);
}
}
public void ApplyLeftSide()
{
- if (LeftSideSelectionViewModel.DataModelPath.GetPropertyType().IsGenericEnumerable())
- {
- if (Parent is DataModelConditionGroupViewModel groupViewModel)
- groupViewModel.ConvertToConditionList(this);
+ Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType();
+ bool converted = ConvertIfRequired(newType);
+ if (converted)
return;
- }
DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
_profileEditorService.UpdateSelectedProfileElement();
@@ -187,12 +180,20 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
Update();
}
+ protected abstract List GetSupportedInputTypes();
+ protected abstract Type GetLeftSideType();
+
+ protected virtual List GetExtraRightSideDataModelViewModels()
+ {
+ return null;
+ }
+
private void ExecuteSelectOperatorCommand(object context)
{
- if (!(context is BaseConditionOperator DataModelConditionOperator))
+ if (!(context is BaseConditionOperator dataModelConditionOperator))
return;
- SelectedOperator = DataModelConditionOperator;
+ SelectedOperator = dataModelConditionOperator;
ApplyOperator();
}
@@ -222,11 +223,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
RightSideSelectionViewModel.DisplaySwitchButton = true;
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested;
+
+ List extra = GetExtraRightSideDataModelViewModels();
+ if (extra != null)
+ RightSideSelectionViewModel.ExtraDataModelViewModels.AddRange(extra);
}
- private void CreateRightSideInputViewModel(Type leftSideType)
+ private void CreateRightSideInputViewModel()
{
- RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription());
+ Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
+ RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(preferredType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription());
RightSideInputViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
RightSideInputViewModel.DisplaySwitchButton = true;
RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
@@ -278,7 +284,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
Update();
}
-
private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e)
{
DataModelConditionPredicate.PredicateType = ProfileRightSideType.Dynamic;
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs
index 5d8908b86..0cae105e4 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs
@@ -1,10 +1,14 @@
-using Artemis.Core;
+using System;
+using Artemis.Core;
+using Artemis.UI.Shared.Input;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
{
public abstract class DataModelConditionViewModel : Conductor.Collection.AllActive
{
+ private DataModelDynamicViewModel _leftSideSelectionViewModel;
+
protected DataModelConditionViewModel(DataModelConditionPart model)
{
Model = model;
@@ -12,6 +16,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
public DataModelConditionPart Model { get; }
+ public DataModelDynamicViewModel LeftSideSelectionViewModel
+ {
+ get => _leftSideSelectionViewModel;
+ set => SetAndNotify(ref _leftSideSelectionViewModel, value);
+ }
+
public abstract void Update();
public virtual void Delete()
@@ -19,5 +29,29 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
Model.Parent.RemoveChild(Model);
((DataModelConditionViewModel) Parent).Update();
}
+
+ protected bool ConvertIfRequired(Type newType)
+ {
+ if (newType == null)
+ return false;
+
+ if (!(Parent is DataModelConditionGroupViewModel groupViewModel))
+ return false;
+
+ // List
+ if (newType.IsGenericEnumerable())
+ {
+ if (this is DataModelConditionListViewModel)
+ return false;
+ groupViewModel.ConvertToConditionList(this);
+ return true;
+ }
+
+ // Predicate
+ if (this is DataModelConditionPredicateViewModel)
+ return false;
+ groupViewModel.ConvertToPredicate(this);
+ return true;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml
new file mode 100644
index 000000000..f71fb5e79
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ triggered
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs
new file mode 100644
index 000000000..324dd7e7e
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using Artemis.Core;
+using Artemis.UI.Ninject.Factories;
+using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services;
+
+namespace Artemis.UI.Screens.ProfileEditor.Conditions
+{
+ public class DataModelConditionEventViewModel : DataModelConditionViewModel, IDisposable
+ {
+ private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
+ private readonly IDataModelUIService _dataModelUIService;
+ private readonly IProfileEditorService _profileEditorService;
+
+ public DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent,
+ IProfileEditorService profileEditorService,
+ IDataModelUIService dataModelUIService,
+ IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionEvent)
+ {
+ _profileEditorService = profileEditorService;
+ _dataModelUIService = dataModelUIService;
+ _dataModelConditionsVmFactory = dataModelConditionsVmFactory;
+ }
+
+ public DataModelConditionEvent DataModelConditionEvent => (DataModelConditionEvent) Model;
+
+ public void Initialize()
+ {
+ LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
+ LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
+ LeftSideSelectionViewModel.LoadEventChildren = false;
+
+ IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
+ List supportedInputTypes = new List {typeof(DataModelEvent), typeof(DataModelEvent<>)};
+
+ LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray();
+ LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(185, 164, 10));
+ LeftSideSelectionViewModel.Placeholder = "Select an event";
+
+ Update();
+ }
+
+ public override void Update()
+ {
+ LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionEvent.EventPath);
+
+ // Remove VMs of effects no longer applied on the layer
+ Items.RemoveRange(Items.Where(c => !DataModelConditionEvent.Children.Contains(c.Model)).ToList());
+
+ if (DataModelConditionEvent.EventPath == null || !DataModelConditionEvent.EventPath.IsValid)
+ return;
+
+ List viewModels = new List();
+ foreach (DataModelConditionPart childModel in Model.Children)
+ {
+ if (Items.Any(c => c.Model == childModel))
+ continue;
+ if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
+ continue;
+
+ DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.Event);
+ viewModel.IsRootGroup = true;
+ viewModels.Add(viewModel);
+ }
+
+ if (viewModels.Any())
+ Items.AddRange(viewModels);
+
+ foreach (DataModelConditionViewModel childViewModel in Items)
+ childViewModel.Update();
+ }
+
+ public void ApplyEvent()
+ {
+ DataModelConditionEvent.UpdateEvent(LeftSideSelectionViewModel.DataModelPath);
+ _profileEditorService.UpdateSelectedProfileElement();
+
+ Update();
+ }
+
+ protected override void OnInitialActivate()
+ {
+ Initialize();
+ base.OnInitialActivate();
+ }
+
+ #region Event handlers
+
+ private void LeftSideSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
+ {
+ ApplyEvent();
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ LeftSideSelectionViewModel.Dispose();
+ LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
index 51db5e501..0e4f1b687 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
@@ -17,6 +17,7 @@
+
@@ -45,13 +46,24 @@
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
index 48dbb28b9..0af34024f 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
@@ -19,14 +19,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
private readonly IProfileEditorService _profileEditorService;
private bool _isInitialized;
private bool _isRootGroup;
+ private bool _isEventGroup;
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
- bool isListGroup,
+ ConditionGroupType groupType,
IProfileEditorService profileEditorService,
IDataModelConditionsVmFactory dataModelConditionsVmFactory)
: base(dataModelConditionGroup)
{
- IsListGroup = isListGroup;
+ GroupType = groupType;
_profileEditorService = profileEditorService;
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
@@ -39,13 +40,25 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
});
}
- public bool IsListGroup { get; }
+ public ConditionGroupType GroupType { get; }
public DataModelConditionGroup DataModelConditionGroup => (DataModelConditionGroup) Model;
public bool IsRootGroup
{
get => _isRootGroup;
- set => SetAndNotify(ref _isRootGroup, value);
+ set
+ {
+ if (!SetAndNotify(ref _isRootGroup, value)) return;
+ NotifyOfPropertyChange(nameof(CanAddEventCondition));
+ }
+ }
+
+ public bool CanAddEventCondition => IsRootGroup && GroupType == ConditionGroupType.General;
+
+ public bool IsEventGroup
+ {
+ get => _isEventGroup;
+ set => SetAndNotify(ref _isEventGroup, value);
}
public bool IsInitialized
@@ -68,10 +81,37 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void AddCondition()
{
- if (!IsListGroup)
- DataModelConditionGroup.AddChild(new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
- else
- DataModelConditionGroup.AddChild(new DataModelConditionListPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
+ switch (GroupType)
+ {
+ case ConditionGroupType.General:
+ DataModelConditionGroup.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
+ break;
+ case ConditionGroupType.List:
+ DataModelConditionGroup.AddChild(new DataModelConditionListPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
+ break;
+ case ConditionGroupType.Event:
+ DataModelConditionGroup.AddChild(new DataModelConditionEventPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ Update();
+ _profileEditorService.UpdateSelectedProfileElement();
+ }
+
+ public void AddEventCondition()
+ {
+ if (!CanAddEventCondition)
+ return;
+
+ // Find a good spot for the event, behind the last existing event
+ int index = 0;
+ DataModelConditionPart existing = DataModelConditionGroup.Children.LastOrDefault(c => c is DataModelConditionEvent);
+ if (existing != null)
+ index = DataModelConditionGroup.Children.IndexOf(existing) + 1;
+
+ DataModelConditionGroup.AddChild(new DataModelConditionEvent(DataModelConditionGroup), index);
Update();
_profileEditorService.UpdateSelectedProfileElement();
@@ -88,11 +128,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public override void Update()
{
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
-
// Remove VMs of effects no longer applied on the layer
Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList());
- List viewModels = new List();
foreach (DataModelConditionPart childModel in Model.Children)
{
if (Items.Any(c => c.Model == childModel))
@@ -100,39 +138,43 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
switch (childModel)
{
- case DataModelConditionGroup DataModelConditionGroup:
- viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataModelConditionGroup, IsListGroup));
+ case DataModelConditionGroup dataModelConditionGroup:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, GroupType));
break;
- case DataModelConditionList DataModelConditionListPredicate:
- viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(DataModelConditionListPredicate));
+ case DataModelConditionList dataModelConditionList:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(dataModelConditionList));
break;
- case DataModelConditionPredicate DataModelConditionPredicate:
- if (!IsListGroup)
- viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionPredicateViewModel(DataModelConditionPredicate));
+ case DataModelConditionEvent dataModelConditionEvent:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent));
break;
- case DataModelConditionListPredicate DataModelConditionListPredicate:
- if (IsListGroup)
- viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(DataModelConditionListPredicate));
+ case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate));
+ break;
+ case DataModelConditionListPredicate dataModelConditionListPredicate:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(dataModelConditionListPredicate));
+ break;
+ case DataModelConditionEventPredicate dataModelConditionEventPredicate:
+ Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventPredicateViewModel(dataModelConditionEventPredicate));
break;
}
}
- if (viewModels.Any())
- Items.AddRange(viewModels);
-
// Ensure the items are in the same order as on the model
((BindableCollection) Items).Sort(i => Model.Children.IndexOf(i.Model));
-
foreach (DataModelConditionViewModel childViewModel in Items)
childViewModel.Update();
- if (IsRootGroup && Parent is DisplayConditionsViewModel displayConditionsViewModel)
- displayConditionsViewModel.DisplayStartHint = !Items.Any();
+ IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel);
+ if (IsEventGroup)
+ {
+ if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And)
+ SelectBooleanOperator("And");
+ }
OnUpdated();
}
- public void ConvertToConditionList(DataModelConditionPredicateViewModel predicateViewModel)
+ public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel)
{
// Store the old index and remove the old predicate
int index = DataModelConditionGroup.Children.IndexOf(predicateViewModel.Model);
@@ -147,15 +189,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
Update();
}
- public void ConvertToPredicate(DataModelConditionListViewModel listViewModel)
+ public void ConvertToPredicate(DataModelConditionViewModel listViewModel)
{
// Store the old index and remove the old predicate
int index = DataModelConditionGroup.Children.IndexOf(listViewModel.Model);
DataModelConditionGroup.RemoveChild(listViewModel.Model);
// Insert a list in the same position
- DataModelConditionPredicate predicate = new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic);
- predicate.UpdateLeftSide(listViewModel.TargetSelectionViewModel.DataModelPath);
+ DataModelConditionGeneralPredicate predicate = new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic);
+ predicate.UpdateLeftSide(listViewModel.LeftSideSelectionViewModel.DataModelPath);
DataModelConditionGroup.AddChild(predicate, index);
// Update to switch the VMs
@@ -169,4 +211,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
Updated?.Invoke(this, EventArgs.Empty);
}
}
+
+ public enum ConditionGroupType
+ {
+ General,
+ List,
+ Event
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs
deleted file mode 100644
index 658ac8c92..000000000
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs
+++ /dev/null
@@ -1,320 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using Artemis.Core;
-using Artemis.Core.Services;
-using Artemis.UI.Exceptions;
-using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
-using Artemis.UI.Shared;
-using Artemis.UI.Shared.Input;
-using Artemis.UI.Shared.Services;
-using Stylet;
-
-namespace Artemis.UI.Screens.ProfileEditor.Conditions
-{
- public class DataModelConditionListPredicateViewModel : DataModelConditionViewModel, IDisposable
- {
- private readonly IConditionOperatorService _conditionOperatorService;
- private readonly IDataModelUIService _dataModelUIService;
- private readonly IProfileEditorService _profileEditorService;
- private bool _isPrimitiveList;
- private DataModelDynamicViewModel _leftSideSelectionViewModel;
- private BindableCollection _operators;
- private DataModelStaticViewModel _rightSideInputViewModel;
- private DataModelDynamicViewModel _rightSideSelectionViewModel;
- private BaseConditionOperator _selectedOperator;
-
- private List _supportedInputTypes;
-
- public DataModelConditionListPredicateViewModel(
- DataModelConditionListPredicate dataModelConditionListPredicate,
- IProfileEditorService profileEditorService,
- IDataModelUIService dataModelUIService,
- IConditionOperatorService conditionOperatorService) : base(dataModelConditionListPredicate)
- {
- _profileEditorService = profileEditorService;
- _dataModelUIService = dataModelUIService;
- _conditionOperatorService = conditionOperatorService;
- _supportedInputTypes = new List();
-
- SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand);
- Operators = new BindableCollection();
-
- Initialize();
- }
-
- public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model;
-
- public BindableCollection Operators
- {
- get => _operators;
- set => SetAndNotify(ref _operators, value);
- }
-
- public DataModelDynamicViewModel LeftSideSelectionViewModel
- {
- get => _leftSideSelectionViewModel;
- set => SetAndNotify(ref _leftSideSelectionViewModel, value);
- }
-
- public DataModelDynamicViewModel RightSideSelectionViewModel
- {
- get => _rightSideSelectionViewModel;
- set => SetAndNotify(ref _rightSideSelectionViewModel, value);
- }
-
- public DataModelStaticViewModel RightSideInputViewModel
- {
- get => _rightSideInputViewModel;
- set => SetAndNotify(ref _rightSideInputViewModel, value);
- }
-
- public BaseConditionOperator SelectedOperator
- {
- get => _selectedOperator;
- set => SetAndNotify(ref _selectedOperator, value);
- }
-
- public DelegateCommand SelectOperatorCommand { get; }
-
- public override void Delete()
- {
- base.Delete();
- _profileEditorService.UpdateSelectedProfileElement();
- }
-
- public void Initialize()
- {
- DataModelPropertiesViewModel listDataModel = GetListDataModel();
- if (listDataModel.Children.Count == 1 && listDataModel.Children.First() is DataModelListPropertyViewModel)
- _isPrimitiveList = true;
- else
- _isPrimitiveList = false;
-
- // Get the data models
- if (!_isPrimitiveList)
- {
- LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
- LeftSideSelectionViewModel.ChangeDataModel(listDataModel);
- LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
- }
-
- // Determine which types are currently supported
- IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
- _supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
- _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
-
- Update();
- }
-
- public override void Update()
- {
- Guid? listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid;
- if (listDataModelGuid == null)
- return;
-
- if (!_isPrimitiveList)
- {
- // Lists use a different color
- LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
- LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
- LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.LeftPath);
- }
-
- Type leftSideType = _isPrimitiveList
- ? DataModelConditionListPredicate.DataModelConditionList.ListType
- : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
-
- // Get the supported operators
- Operators.Clear();
- Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
- if (DataModelConditionListPredicate.Operator == null)
- DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object), ConditionParameterSide.Left)));
- SelectedOperator = DataModelConditionListPredicate.Operator;
-
- // Without a selected operator or one that supports a right side, leave the right side input empty
- if (SelectedOperator == null || SelectedOperator.RightSideType == null)
- {
- DisposeRightSideStaticViewModel();
- DisposeRightSideDynamicViewModel();
- return;
- }
-
- // Ensure the right side has the proper VM
- if (DataModelConditionListPredicate.PredicateType == ProfileRightSideType.Dynamic)
- {
- DisposeRightSideStaticViewModel();
- if (RightSideSelectionViewModel == null)
- CreateRightSideSelectionViewModel();
-
- RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.RightPath);
- RightSideSelectionViewModel.FilterTypes = new[] {SelectedOperator.RightSideType};
- }
- else
- {
- DisposeRightSideDynamicViewModel();
- if (RightSideInputViewModel == null)
- CreateRightSideInputViewModel(SelectedOperator.RightSideType);
-
- if (SelectedOperator.RightSideType.IsValueType && DataModelConditionListPredicate.RightStaticValue == null)
- RightSideInputViewModel.Value = SelectedOperator.RightSideType.GetDefault();
- else
- RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue;
- if (RightSideInputViewModel.TargetType != SelectedOperator.RightSideType)
- RightSideInputViewModel.UpdateTargetType(SelectedOperator.RightSideType);
- }
- }
-
- public void ApplyLeftSide()
- {
- DataModelConditionListPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
- _profileEditorService.UpdateSelectedProfileElement();
-
- SelectedOperator = DataModelConditionListPredicate.Operator;
- Update();
- }
-
- public void ApplyRightSideDynamic()
- {
- DataModelConditionListPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath);
- _profileEditorService.UpdateSelectedProfileElement();
-
- Update();
- }
-
- public void ApplyRightSideStatic(object value)
- {
- DataModelConditionListPredicate.UpdateRightSideStatic(value);
- _profileEditorService.UpdateSelectedProfileElement();
-
- Update();
- }
-
- public void ApplyOperator()
- {
- DataModelConditionListPredicate.UpdateOperator(SelectedOperator);
- _profileEditorService.UpdateSelectedProfileElement();
-
- Update();
- }
-
- private DataModelPropertiesViewModel GetListDataModel()
- {
- if (DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid == null)
- throw new ArtemisUIException("Failed to retrieve the list data model VM for this list predicate because it has no list path");
-
- DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true);
- DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath(
- DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid.Value,
- DataModelConditionListPredicate.DataModelConditionList.ListPath.Path
- );
-
- return listDataModel.GetListTypeViewModel(_dataModelUIService);
- }
-
- private void ExecuteSelectOperatorCommand(object context)
- {
- if (!(context is BaseConditionOperator dataModelConditionOperator))
- return;
-
- SelectedOperator = dataModelConditionOperator;
- ApplyOperator();
- }
-
- #region IDisposable
-
- public void Dispose()
- {
- if (!_isPrimitiveList)
- {
- LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected;
- LeftSideSelectionViewModel.Dispose();
- }
-
- DisposeRightSideStaticViewModel();
- DisposeRightSideDynamicViewModel();
- }
-
- #endregion
-
- #region Event handlers
-
- private void LeftSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
- {
- ApplyLeftSide();
- }
-
- private void RightSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
- {
- ApplyRightSideDynamic();
- }
-
- private void RightSideOnValueEntered(object sender, DataModelInputStaticEventArgs e)
- {
- ApplyRightSideStatic(e.Value);
- }
-
- private void RightSideSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e)
- {
- DataModelConditionListPredicate.PredicateType = ProfileRightSideType.Static;
- Update();
- }
-
- private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e)
- {
- DataModelConditionListPredicate.PredicateType = ProfileRightSideType.Dynamic;
- Update();
- }
-
- #endregion
-
- #region View model management
-
- private void CreateRightSideSelectionViewModel()
- {
- RightSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
- RightSideSelectionViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
- RightSideSelectionViewModel.DisplaySwitchButton = true;
- RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
- RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested;
-
- // Add an extra data model to the selection VM to allow self-referencing the current item
- // The safe cast prevents adding this extra VM on primitive lists where they serve no purpose
- if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue)
- RightSideSelectionViewModel.ExtraDataModelViewModels.Add(listValue);
- }
-
- private void CreateRightSideInputViewModel(Type leftSideType)
- {
- RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription());
- RightSideInputViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
- RightSideInputViewModel.DisplaySwitchButton = true;
- RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
- RightSideInputViewModel.SwitchToDynamicRequested += RightSideInputViewModelOnSwitchToDynamicRequested;
- }
-
- private void DisposeRightSideStaticViewModel()
- {
- if (RightSideInputViewModel == null)
- return;
- RightSideInputViewModel.ValueUpdated -= RightSideOnValueEntered;
- RightSideInputViewModel.SwitchToDynamicRequested -= RightSideInputViewModelOnSwitchToDynamicRequested;
- RightSideInputViewModel.Dispose();
- RightSideInputViewModel = null;
- }
-
- private void DisposeRightSideDynamicViewModel()
- {
- if (RightSideSelectionViewModel == null)
- return;
- RightSideSelectionViewModel.PropertySelected -= RightSideOnPropertySelected;
- RightSideSelectionViewModel.SwitchToStaticRequested -= RightSideSelectionViewModelOnSwitchToStaticRequested;
- RightSideSelectionViewModel.Dispose();
- RightSideSelectionViewModel = null;
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml
index 7ad1e3265..f19dd3a0c 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml
@@ -16,7 +16,7 @@
-
+
@@ -41,7 +41,7 @@
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs
index ba875de53..c1da5d680 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
@@ -7,7 +6,6 @@ using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
using Artemis.UI.Shared;
-using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Services;
using Humanizer;
@@ -18,7 +16,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService;
- private DataModelDynamicViewModel _targetSelectionViewModel;
public DataModelConditionListViewModel(
DataModelConditionList dataModelConditionList,
@@ -29,26 +26,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
_profileEditorService = profileEditorService;
_dataModelUIService = dataModelUIService;
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
-
- Initialize();
- }
-
- public DataModelDynamicViewModel TargetSelectionViewModel
- {
- get => _targetSelectionViewModel;
- set => SetAndNotify(ref _targetSelectionViewModel, value);
}
public DataModelConditionList DataModelConditionList => (DataModelConditionList) Model;
public string SelectedListOperator => DataModelConditionList.ListOperator.Humanize();
- public void Dispose()
- {
- TargetSelectionViewModel.Dispose();
- TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected;
- }
-
public void SelectListOperator(string type)
{
ListOperator enumValue = Enum.Parse(type);
@@ -60,7 +43,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void AddCondition()
{
- DataModelConditionList.AddChild(new DataModelConditionPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
+ DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
Update();
_profileEditorService.UpdateSelectedProfileElement();
@@ -82,38 +65,37 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void Initialize()
{
- TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
- TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected;
+ LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
+ LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
List supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
supportedInputTypes.Add(typeof(IEnumerable<>));
- TargetSelectionViewModel.FilterTypes = supportedInputTypes.ToArray();
- TargetSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
- TargetSelectionViewModel.Placeholder = "Select a list";
+ LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray();
+ LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
+ LeftSideSelectionViewModel.Placeholder = "Select a list";
Update();
}
public void ApplyList()
{
- if (!TargetSelectionViewModel.DataModelPath.GetPropertyType().IsGenericEnumerable())
- {
- if (Parent is DataModelConditionGroupViewModel groupViewModel)
- groupViewModel.ConvertToPredicate(this);
+ Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType();
+ bool converted = ConvertIfRequired(newType);
+ if (converted)
return;
- }
- DataModelConditionList.UpdateList(TargetSelectionViewModel.DataModelPath);
+
+ DataModelConditionList.UpdateList(LeftSideSelectionViewModel.DataModelPath);
_profileEditorService.UpdateSelectedProfileElement();
Update();
}
-
+
public override void Update()
{
- TargetSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath);
+ LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath);
NotifyOfPropertyChange(nameof(SelectedListOperator));
// Remove VMs of effects no longer applied on the layer
@@ -130,7 +112,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
continue;
- DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, true);
+ DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.List);
viewModel.IsRootGroup = true;
viewModels.Add(viewModel);
}
@@ -142,9 +124,21 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
childViewModel.Update();
}
- private void TargetSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
+ protected override void OnInitialActivate()
+ {
+ Initialize();
+ base.OnInitialActivate();
+ }
+
+ private void LeftSideSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
{
ApplyList();
}
+
+ public void Dispose()
+ {
+ LeftSideSelectionViewModel.Dispose();
+ LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml
new file mode 100644
index 000000000..3fb1a1bd9
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs
similarity index 75%
rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs
rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs
index 0891cd5fc..5ce43cf28 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs
@@ -4,11 +4,11 @@ using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.Conditions
{
///
- /// Interaction logic for DataModelConditionPredicateView.xaml
+ /// Interaction logic for DataModelConditionEventPredicateView.xaml
///
- public partial class DataModelConditionPredicateView : UserControl
+ public partial class DataModelConditionEventPredicateView : UserControl
{
- public DataModelConditionPredicateView()
+ public DataModelConditionEventPredicateView()
{
InitializeComponent();
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs
new file mode 100644
index 000000000..75326fef2
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services;
+
+namespace Artemis.UI.Screens.ProfileEditor.Conditions
+{
+ public class DataModelConditionEventPredicateViewModel : DataModelConditionPredicateViewModel
+ {
+ private readonly IDataModelUIService _dataModelUIService;
+
+ public DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate,
+ IProfileEditorService profileEditorService,
+ IDataModelUIService dataModelUIService,
+ IConditionOperatorService conditionOperatorService,
+ ISettingsService settingsService)
+ : base(dataModelConditionEventPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
+ {
+ _dataModelUIService = dataModelUIService;
+
+ LeftSideColor = new SolidColorBrush(Color.FromRgb(185, 164, 10));
+ }
+
+ public DataModelConditionEventPredicate DataModelConditionEventPredicate => (DataModelConditionEventPredicate) Model;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ DataModelPropertiesViewModel eventDataModel = GetEventDataModel();
+ LeftSideSelectionViewModel.ChangeDataModel(eventDataModel);
+ }
+
+ protected override void OnInitialActivate()
+ {
+ base.OnInitialActivate();
+ Initialize();
+ }
+
+ protected override List GetSupportedInputTypes()
+ {
+ IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
+ List supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
+ supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
+
+ return supportedInputTypes;
+ }
+
+ protected override Type GetLeftSideType()
+ {
+ return LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
+ }
+
+ protected override List GetExtraRightSideDataModelViewModels()
+ {
+ // Extra data models are expected to not have an empty root, so lets return the child
+ return GetEventDataModel().Children.Cast().ToList();
+ }
+
+ private DataModelPropertiesViewModel GetEventDataModel()
+ {
+ EventPredicateWrapperDataModel wrapper = EventPredicateWrapperDataModel.Create(
+ DataModelConditionEventPredicate.DataModelConditionEvent.EventArgumentType
+ );
+
+ return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(false));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml
similarity index 97%
rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml
rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml
index 8bdc6a14e..dfcac88d1 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml
@@ -7,10 +7,10 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
xmlns:conditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
- x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionPredicateView"
+ x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionGeneralPredicateView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
- d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type conditions:DataModelConditionPredicateViewModel}}">
+ d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type conditions:DataModelConditionGeneralPredicateViewModel}}">
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs
new file mode 100644
index 000000000..22411462b
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs
@@ -0,0 +1,26 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Artemis.UI.Screens.ProfileEditor.Conditions
+{
+ ///
+ /// Interaction logic for DataModelConditionGeneralPredicateView.xaml
+ ///
+ public partial class DataModelConditionGeneralPredicateView : UserControl
+ {
+ public DataModelConditionGeneralPredicateView()
+ {
+ InitializeComponent();
+ }
+
+ private void PropertyButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ // DataContext is not set when using left button, I don't know why but there it is
+ if (sender is Button button && button.ContextMenu != null)
+ {
+ button.ContextMenu.DataContext = button.DataContext;
+ button.ContextMenu.IsOpen = true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs
new file mode 100644
index 000000000..5c7f781c5
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services;
+
+namespace Artemis.UI.Screens.ProfileEditor.Conditions
+{
+ public class DataModelConditionGeneralPredicateViewModel : DataModelConditionPredicateViewModel
+ {
+ private readonly IDataModelUIService _dataModelUIService;
+
+ public DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate,
+ IProfileEditorService profileEditorService,
+ IDataModelUIService dataModelUIService,
+ IConditionOperatorService conditionOperatorService,
+ ISettingsService settingsService)
+ : base(dataModelConditionGeneralPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
+ {
+ _dataModelUIService = dataModelUIService;
+ }
+
+ protected override void OnInitialActivate()
+ {
+ Initialize();
+ }
+
+ protected override List GetSupportedInputTypes()
+ {
+ IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
+ List supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
+ supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
+ supportedInputTypes.Add(typeof(IEnumerable<>));
+
+ return supportedInputTypes;
+ }
+
+ protected override Type GetLeftSideType()
+ {
+ return LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml
similarity index 83%
rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml
rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml
index 7b6ba3bb8..b22c824d3 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml
@@ -5,7 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
- xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
xmlns:DataModelConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionListPredicateView"
@@ -17,21 +16,12 @@
-
-
-
-
-
-
-
-
-
-
+
@@ -50,7 +40,7 @@
-
+
(DataModelConditionListPredicate) Model;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ DataModelPropertiesViewModel listDataModel = GetListDataModel();
+ LeftSideSelectionViewModel.ChangeDataModel(listDataModel);
+
+ // If this is a primitive list the user doesn't have much to choose, so preselect the list item for them
+ if (DataModelConditionListPredicate.DataModelConditionList.IsPrimitiveList && DataModelConditionListPredicate.LeftPath == null)
+ {
+ DataModelConditionListPredicate.UpdateLeftSide(listDataModel.Children.FirstOrDefault()?.DataModelPath);
+ Update();
+ }
+ }
+
+ protected override void OnInitialActivate()
+ {
+ base.OnInitialActivate();
+ Initialize();
+ }
+
+ protected override List GetSupportedInputTypes()
+ {
+ IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors;
+ List supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
+ supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
+
+ return supportedInputTypes;
+ }
+
+ protected override Type GetLeftSideType()
+ {
+ return DataModelConditionListPredicate.DataModelConditionList.IsPrimitiveList
+ ? DataModelConditionListPredicate.DataModelConditionList.ListType
+ : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
+ }
+
+ protected override List GetExtraRightSideDataModelViewModels()
+ {
+ if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue)
+ return new List {listValue};
+ return null;
+ }
+
+ private DataModelPropertiesViewModel GetListDataModel()
+ {
+ ListPredicateWrapperDataModel wrapper = ListPredicateWrapperDataModel.Create(
+ DataModelConditionListPredicate.DataModelConditionList.ListType
+ );
+
+ return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml
index 838d58ddf..cc49f3d59 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml
@@ -7,12 +7,14 @@
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
+ xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}">
+
@@ -56,9 +58,9 @@
-
+
-
+
@@ -67,19 +69,15 @@
-
-
-
-
-
-
- Configure how the layer should act while the conditions above are met
-
-
-
-
-
-
+
+
+
+
+ Configure how the layer should act while the conditions above are met
+
+
+
+
@@ -98,10 +96,10 @@
-
-
- REPEAT
-
+
+
+ REPEAT
+
-
-
- ONCE
-
+
+
+ ONCE
+
-
-
-
-
-
-
- Configure how the layer should act when the conditions above are no longer met
-
-
-
-
-
+
+
+
+
+ Configure how the layer should act when the conditions above are no longer met
+
+
+
+
@@ -154,10 +149,10 @@
-
-
- FINISH
-
+
+
+ FINISH
+
-
-
- SKIP TO END
-
+
+
+ SKIP TO END
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Configure how the layer should act when the event(s) trigger before the timeline finishes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RESTART
+
+
+
+
+ Stop the current run and restart the timeline
+
+
+
+
+
+
+
+ IGNORE
+
+
+
+
+ Ignore subsequent event fires until the timeline finishes
+
+
+
+
+
+
+
+ COPY
+
+
+
+
+ Play another copy of the timeline on top of the current run
+
+
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs
index b5f75e0c9..c4646dd37 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Conditions;
@@ -14,6 +15,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
private readonly IProfileEditorService _profileEditorService;
private RenderProfileElement _renderProfileElement;
private bool _displayStartHint;
+ private bool _isEventCondition;
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDataModelConditionsVmFactory dataModelConditionsVmFactory)
{
@@ -27,6 +29,12 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
set => SetAndNotify(ref _displayStartHint, value);
}
+ public bool IsEventCondition
+ {
+ get => _isEventCondition;
+ set => SetAndNotify(ref _isEventCondition, value);
+ }
+
public RenderProfileElement RenderProfileElement
{
get => _renderProfileElement;
@@ -35,22 +43,24 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
public bool DisplayContinuously
{
- get => RenderProfileElement?.DisplayContinuously ?? false;
+ get => RenderProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
set
{
- if (RenderProfileElement == null || RenderProfileElement.DisplayContinuously == value) return;
- RenderProfileElement.DisplayContinuously = value;
+ TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
+ if (RenderProfileElement == null || RenderProfileElement?.Timeline.PlayMode == playMode) return;
+ RenderProfileElement.Timeline.PlayMode = playMode;
_profileEditorService.UpdateSelectedProfileElement();
}
}
public bool AlwaysFinishTimeline
{
- get => RenderProfileElement?.AlwaysFinishTimeline ?? false;
+ get => RenderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish;
set
{
- if (RenderProfileElement == null || RenderProfileElement.AlwaysFinishTimeline == value) return;
- RenderProfileElement.AlwaysFinishTimeline = value;
+ TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd;
+ if (RenderProfileElement == null || RenderProfileElement?.Timeline.StopMode == stopMode) return;
+ RenderProfileElement.Timeline.StopMode = stopMode;
_profileEditorService.UpdateSelectedProfileElement();
}
}
@@ -71,7 +81,14 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
{
+ if (RenderProfileElement != null)
+ {
+ RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
+ RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
+ }
+
RenderProfileElement = e.RenderProfileElement;
+
NotifyOfPropertyChange(nameof(DisplayContinuously));
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled));
@@ -86,12 +103,21 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
if (e.RenderProfileElement.DisplayCondition == null)
e.RenderProfileElement.DisplayCondition = new DataModelConditionGroup(null);
- ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(e.RenderProfileElement.DisplayCondition, false);
+ ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(e.RenderProfileElement.DisplayCondition, ConditionGroupType.General);
ActiveItem.IsRootGroup = true;
ActiveItem.Update();
- // Only show the intro to conditions once, and only if the layer has no conditions
- DisplayStartHint = !ActiveItem.Items.Any();
+ DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
+ IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
+
+ RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified;
+ RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
+ }
+
+ private void DisplayConditionOnChildrenModified(object? sender, EventArgs e)
+ {
+ DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
+ IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
index f28be532a..bed240696 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
@@ -5,18 +5,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
- xmlns:dd="urn:gong-wpf-dragdrop" xmlns:Converters="clr-namespace:Artemis.UI.Converters"
- xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
+ xmlns:dd="urn:gong-wpf-dragdrop"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
-
-
-
-
@@ -27,43 +22,16 @@
: Screen, IDataBindingModeViewModel
+ public class ConditionalDataBindingModeViewModel : Conductor>.Collection.AllActive, IDataBindingModeViewModel
{
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IProfileEditorService _profileEditorService;
@@ -24,18 +24,83 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
_dataBindingsVmFactory = dataBindingsVmFactory;
ConditionalDataBinding = conditionalDataBinding;
- ConditionViewModels = new BindableCollection>();
-
- Initialize();
}
public ConditionalDataBinding ConditionalDataBinding { get; }
- public BindableCollection> ConditionViewModels { get; }
+
+ public void AddCondition()
+ {
+ DataBindingCondition condition = ConditionalDataBinding.AddCondition();
+
+ // Find the VM of the new condition
+ DataBindingConditionViewModel viewModel = Items.First(c => c.DataBindingCondition == condition);
+ viewModel.ActiveItem.AddCondition();
+
+ _profileEditorService.UpdateSelectedProfileElement();
+ }
+
+ protected override void OnInitialActivate()
+ {
+ base.OnInitialActivate();
+ Initialize();
+ }
+
+ private void UpdateItems()
+ {
+ _updating = true;
+
+ // Remove old VMs
+ List> toRemove = Items.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList();
+ foreach (DataBindingConditionViewModel dataBindingConditionViewModel in toRemove)
+ {
+ Items.Remove(dataBindingConditionViewModel);
+ dataBindingConditionViewModel.Dispose();
+ }
+
+ // Add missing VMs
+ foreach (DataBindingCondition condition in ConditionalDataBinding.Conditions)
+ if (Items.All(c => c.DataBindingCondition != condition))
+ Items.Add(_dataBindingsVmFactory.DataBindingConditionViewModel(condition));
+
+ // Fix order
+ ((BindableCollection>) Items).Sort(c => c.DataBindingCondition.Order);
+
+ _updating = false;
+ }
+
+ private void Initialize()
+ {
+ ConditionalDataBinding.ConditionsUpdated += ConditionalDataBindingOnConditionsUpdated;
+ Items.CollectionChanged += ItemsOnCollectionChanged;
+ UpdateItems();
+ }
+
+ private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (_updating || e.Action != NotifyCollectionChangedAction.Add)
+ return;
+
+ for (int index = 0; index < Items.Count; index++)
+ {
+ DataBindingConditionViewModel conditionViewModel = Items[index];
+ conditionViewModel.DataBindingCondition.Order = index + 1;
+ }
+
+ ConditionalDataBinding.ApplyOrder();
+
+ _profileEditorService.UpdateSelectedProfileElement();
+ }
+
+ private void ConditionalDataBindingOnConditionsUpdated(object sender, EventArgs e)
+ {
+ UpdateItems();
+ }
+
public bool SupportsTestValue => false;
public void Update()
{
- UpdateConditionViewModels();
+ UpdateItems();
}
public object GetTestValue()
@@ -49,64 +114,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
{
ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated;
- foreach (DataBindingConditionViewModel conditionViewModel in ConditionViewModels)
+ foreach (DataBindingConditionViewModel conditionViewModel in Items)
conditionViewModel.Dispose();
- ConditionViewModels.Clear();
+ Items.Clear();
}
#endregion
-
- private void UpdateConditionViewModels()
- {
- _updating = true;
-
- // Remove old VMs
- List> toRemove = ConditionViewModels.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList();
- foreach (DataBindingConditionViewModel dataBindingConditionViewModel in toRemove)
- {
- ConditionViewModels.Remove(dataBindingConditionViewModel);
- dataBindingConditionViewModel.Dispose();
- }
-
- // Add missing VMs
- foreach (DataBindingCondition condition in ConditionalDataBinding.Conditions)
- {
- if (ConditionViewModels.All(c => c.DataBindingCondition != condition))
- ConditionViewModels.Add(_dataBindingsVmFactory.DataBindingConditionViewModel(condition));
- }
-
- // Fix order
- ConditionViewModels.Sort(c => c.DataBindingCondition.Order);
-
- _updating = false;
- }
-
- private void Initialize()
- {
- ConditionalDataBinding.ConditionsUpdated += ConditionalDataBindingOnConditionsUpdated;
- ConditionViewModels.CollectionChanged += ConditionViewModelsOnCollectionChanged;
- UpdateConditionViewModels();
- }
-
- private void ConditionViewModelsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if (_updating || e.Action != NotifyCollectionChangedAction.Add)
- return;
-
- for (int index = 0; index < ConditionViewModels.Count; index++)
- {
- DataBindingConditionViewModel conditionViewModel = ConditionViewModels[index];
- conditionViewModel.DataBindingCondition.Order = index + 1;
- }
-
- ConditionalDataBinding.ApplyOrder();
-
- _profileEditorService.UpdateSelectedProfileElement();
- }
-
- private void ConditionalDataBindingOnConditionsUpdated(object sender, EventArgs e)
- {
- UpdateConditionViewModels();
- }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
index 4b068ef90..f3e1833c6 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
@@ -18,7 +18,7 @@
IsTabStop="False" />
: Conductor, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
+ private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
+ private readonly IDataModelUIService _dataModelUIService;
public DataBindingConditionViewModel(DataBindingCondition dataBindingCondition,
IProfileEditorService profileEditorService,
@@ -20,22 +22,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
IDataModelUIService dataModelUIService)
{
_profileEditorService = profileEditorService;
+ _dataModelConditionsVmFactory = dataModelConditionsVmFactory;
+ _dataModelUIService = dataModelUIService;
DataBindingCondition = dataBindingCondition;
-
- ActiveItem = dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataBindingCondition.Condition, false);
- ActiveItem.IsRootGroup = true;
- ActiveItem.Update();
- ActiveItem.Updated += ActiveItemOnUpdated;
-
- ValueViewModel = dataModelUIService.GetStaticInputViewModel(typeof(TProperty), null);
- ValueViewModel.ValueUpdated += ValueViewModelOnValueUpdated;
- ValueViewModel.Value = DataBindingCondition.Value;
}
public DataBindingCondition DataBindingCondition { get; }
public DataModelStaticViewModel ValueViewModel { get; set; }
+ protected override void OnInitialActivate()
+ {
+ base.OnInitialActivate();
+ ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataBindingCondition.Condition, ConditionGroupType.General);
+ ActiveItem.IsRootGroup = true;
+ ActiveItem.Update();
+ ActiveItem.Updated += ActiveItemOnUpdated;
+
+ ValueViewModel = _dataModelUIService.GetStaticInputViewModel(typeof(TProperty), null);
+ ValueViewModel.ValueUpdated += ValueViewModelOnValueUpdated;
+ ValueViewModel.Value = DataBindingCondition.Value;
+ }
+
public void Dispose()
{
ValueViewModel.Dispose();
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
index 153e9ca40..95d873b28 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
@@ -44,7 +44,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
EasingViewModels = new BindableCollection();
TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
+ }
+ protected override void OnInitialActivate()
+ {
+ base.OnInitialActivate();
Initialize();
}
@@ -237,7 +241,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
}
// While playing in preview data bindings aren't updated
- Registration.DataBinding.Update(0.04);
+ Registration.DataBinding.UpdateWithDelta(TimeSpan.FromMilliseconds(40));
if (ActiveItem.SupportsTestValue)
{
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs
index 81e4c9e12..bfe1f5a54 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs
@@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
// and creating the actual data bindings
foreach (IDataBindingRegistration registration in registrations)
Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration));
-
+
SelectedItemIndex = 0;
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs
index a77871291..65620f0e5 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs
@@ -140,7 +140,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
if (DynamicSelectionViewModel != null)
DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
else if (StaticInputViewModel != null)
+ {
+ // Ensure the right static value is never null when the preferred type is a value type
StaticInputViewModel.Value = Modifier.ParameterStaticValue;
+ if (SelectedModifierType.ParameterType.IsValueType && StaticInputViewModel.Value == null)
+ StaticInputViewModel.Value = SelectedModifierType.ParameterType.GetDefault();
+ }
}
private void ExecuteSelectModifierTypeCommand(object context)
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index 3ed17124f..41401ab7e 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -567,7 +567,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{
if (Repeating && RepeatTimeline)
{
- if (newTime > SelectedProfileElement.TimelineLength)
+ if (newTime > SelectedProfileElement.Timeline.Length)
newTime = TimeSpan.Zero;
}
else if (Repeating && RepeatSegment)
@@ -575,9 +575,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
if (newTime > GetCurrentSegmentEnd())
newTime = GetCurrentSegmentStart();
}
- else if (newTime > SelectedProfileElement.TimelineLength)
+ else if (newTime > SelectedProfileElement.Timeline.Length)
{
- newTime = SelectedProfileElement.TimelineLength;
+ newTime = SelectedProfileElement.Timeline.Length;
Pause();
}
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
index 92e96040b..6c9b8e7d5 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
@@ -148,8 +148,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
if (position < TimeSpan.Zero)
LayerPropertyKeyframe.Position = TimeSpan.Zero;
- else if (position > _profileEditorService.SelectedProfileElement.TimelineLength)
- LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength;
+ else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
+ LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
else
LayerPropertyKeyframe.Position = position;
@@ -170,7 +170,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
);
// If possible, shift the keyframe to the right by 11 pixels
TimeSpan desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
- if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength)
+ if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.Timeline.Length)
newKeyframe.Position = desiredPosition;
// Otherwise if possible shift it to the left by 11 pixels
else
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs
index 888d0db50..066b66cdb 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs
@@ -56,10 +56,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue;
- List> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
+ List> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe keyframe in toShift)
keyframe.Position += amount;
-
+
UpdateKeyframes();
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
index 578bbf7a2..974a64b66 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
@@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
if (ProfileEditorService.SelectedProfileElement != null)
- ProfileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
+ ProfileEditorService.SelectedProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update();
}
@@ -104,15 +104,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
if (Segment != SegmentViewModelType.Main)
return false;
-
- return SelectedProfileElement?.DisplayContinuously ?? false;
+ return SelectedProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
}
set
{
- if (Segment != SegmentViewModelType.Main)
+ if (Segment != SegmentViewModelType.Main || SelectedProfileElement == null)
return;
-
- SelectedProfileElement.DisplayContinuously = value;
+ SelectedProfileElement.Timeline.PlayMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
ProfileEditorService.UpdateSelectedProfileElement();
NotifyOfPropertyChange(nameof(RepeatSegment));
}
@@ -158,32 +156,35 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
if (SelectedProfileElement == null)
{
- SegmentLength = TimeSpan.Zero;
SegmentStart = TimeSpan.Zero;
SegmentEnd = TimeSpan.Zero;
+ SegmentLength = TimeSpan.Zero;
SegmentStartPosition = 0;
SegmentWidth = 0;
SegmentEnabled = false;
return;
}
+ // It would be nice to do this differently if this patterns end up appearing in more places
if (Segment == SegmentViewModelType.Start)
{
- SegmentLength = SelectedProfileElement.StartSegmentLength;
SegmentStart = TimeSpan.Zero;
+ SegmentEnd = SelectedProfileElement.Timeline.StartSegmentEndPosition;
+ SegmentLength = SelectedProfileElement.Timeline.StartSegmentLength;
}
else if (Segment == SegmentViewModelType.Main)
{
- SegmentLength = SelectedProfileElement.MainSegmentLength;
- SegmentStart = SelectedProfileElement.StartSegmentLength;
+ SegmentStart = SelectedProfileElement.Timeline.MainSegmentStartPosition;
+ SegmentEnd = SelectedProfileElement.Timeline.MainSegmentEndPosition;
+ SegmentLength = SelectedProfileElement.Timeline.MainSegmentLength;
}
else if (Segment == SegmentViewModelType.End)
{
- SegmentLength = SelectedProfileElement.EndSegmentLength;
- SegmentStart = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
+ SegmentStart = SelectedProfileElement.Timeline.EndSegmentStartPosition;
+ SegmentEnd = SelectedProfileElement.Timeline.EndSegmentEndPosition;
+ SegmentLength = SelectedProfileElement.Timeline.EndSegmentLength;
}
- SegmentEnd = SegmentStart + SegmentLength;
SegmentStartPosition = SegmentStart.TotalSeconds * ProfileEditorService.PixelsPerSecond;
SegmentWidth = SegmentLength.TotalSeconds * ProfileEditorService.PixelsPerSecond;
SegmentEnabled = SegmentLength != TimeSpan.Zero;
@@ -197,28 +198,25 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void DisableSegment()
{
- TimeSpan startSegmentEnd = SelectedProfileElement.StartSegmentLength;
- TimeSpan mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
-
TimeSpan oldSegmentLength = SegmentLength;
if (Segment == SegmentViewModelType.Start)
{
// Remove keyframes that fall in this segment
- WipeKeyframes(null, startSegmentEnd);
- SelectedProfileElement.StartSegmentLength = TimeSpan.Zero;
+ WipeKeyframes(null, SelectedProfileElement.Timeline.StartSegmentEndPosition);
+ SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.Zero;
}
else if (Segment == SegmentViewModelType.Main)
{
// Remove keyframes that fall in this segment
- WipeKeyframes(startSegmentEnd, startSegmentEnd);
- SelectedProfileElement.MainSegmentLength = TimeSpan.Zero;
+ WipeKeyframes(SelectedProfileElement.Timeline.MainSegmentStartPosition, SelectedProfileElement.Timeline.MainSegmentEndPosition);
+ SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.Zero;
}
else if (Segment == SegmentViewModelType.End)
{
// Remove keyframes that fall in this segment
- WipeKeyframes(mainSegmentEnd, null);
- SelectedProfileElement.EndSegmentLength = TimeSpan.Zero;
+ WipeKeyframes(SelectedProfileElement.Timeline.EndSegmentStartPosition, SelectedProfileElement.Timeline.EndSegmentEndPosition);
+ SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.Zero;
}
ShiftNextSegment(SegmentLength - oldSegmentLength);
@@ -230,11 +228,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
ShiftNextSegment(TimeSpan.FromSeconds(1));
if (Segment == SegmentViewModelType.Start)
- SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1);
+ SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.Main)
- SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1);
+ SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.End)
- SelectedProfileElement.EndSegmentLength = TimeSpan.FromSeconds(1);
+ SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.FromSeconds(1);
ProfileEditorService.UpdateSelectedProfileElement();
Update();
@@ -292,27 +290,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void UpdateLength(TimeSpan newTime)
{
- TimeSpan oldSegmentLength = SegmentLength;
- if (Segment == SegmentViewModelType.Start)
- {
- if (newTime < TimeSpan.FromMilliseconds(100))
- newTime = TimeSpan.FromMilliseconds(100);
- SelectedProfileElement.StartSegmentLength = newTime;
- }
- else if (Segment == SegmentViewModelType.Main)
- {
- if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100))
- newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100);
- SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength;
- }
- else if (Segment == SegmentViewModelType.End)
- {
- if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100))
- newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100);
- SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
- }
+ if (newTime < TimeSpan.FromMilliseconds(100))
+ newTime = TimeSpan.FromMilliseconds(100);
+
+ TimeSpan difference = newTime - SegmentEnd;
+
+ if (difference > TimeSpan.Zero)
+ ShiftNextSegment(difference);
+
+ if (Segment == SegmentViewModelType.Start)
+ SelectedProfileElement.Timeline.StartSegmentEndPosition = newTime;
+ else if (Segment == SegmentViewModelType.Main)
+ SelectedProfileElement.Timeline.MainSegmentEndPosition = newTime;
+ else if (Segment == SegmentViewModelType.End)
+ SelectedProfileElement.Timeline.EndSegmentEndPosition = newTime;
+
+ if (difference < TimeSpan.Zero)
+ ShiftNextSegment(difference);
- ShiftNextSegment(SegmentLength - oldSegmentLength);
Update();
}
@@ -325,7 +320,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
if (SelectedProfileElement != null)
- SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
+ SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
}
#endregion
@@ -335,20 +330,20 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e)
{
if (e.PreviousRenderProfileElement != null)
- e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
+ e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
if (e.RenderProfileElement != null)
- e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
+ e.RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update();
}
- private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) ||
- e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) ||
- e.PropertyName == nameof(RenderProfileElement.EndSegmentLength))
+ if (e.PropertyName == nameof(Core.Timeline.StartSegmentLength) ||
+ e.PropertyName == nameof(Core.Timeline.MainSegmentLength) ||
+ e.PropertyName == nameof(Core.Timeline.EndSegmentLength))
Update();
- else if (e.PropertyName == nameof(RenderProfileElement.DisplayContinuously))
+ else if (e.PropertyName == nameof(Core.Timeline.PlayMode))
NotifyOfPropertyChange(nameof(RepeatSegment));
}
@@ -361,15 +356,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void ShiftNextSegment(TimeSpan amount)
{
- TimeSpan segmentEnd = TimeSpan.Zero;
if (Segment == SegmentViewModelType.Start)
- segmentEnd = SelectedProfileElement.StartSegmentLength;
+ ShiftKeyframes(SelectedProfileElement.Timeline.StartSegmentEndPosition, null, amount);
else if (Segment == SegmentViewModelType.Main)
- segmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
+ ShiftKeyframes(SelectedProfileElement.Timeline.MainSegmentEndPosition, null, amount);
else if (Segment == SegmentViewModelType.End)
- segmentEnd = SelectedProfileElement.TimelineLength;
-
- ShiftKeyframes(segmentEnd, null, amount);
+ ShiftKeyframes(SelectedProfileElement.Timeline.EndSegmentEndPosition, null, amount);
}
private void WipeKeyframes(TimeSpan? start, TimeSpan? end)
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs
index 83c271cb4..5dd9a62b9 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs
@@ -29,7 +29,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
_profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
if (_profileEditorService.SelectedProfileElement != null)
- _profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
+ _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update();
}
@@ -75,9 +75,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
TotalTimelineWidth = EndSegmentEndPosition;
}
- private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- Update();
+ if (e.PropertyName == nameof(Core.Timeline.StartSegmentLength) ||
+ e.PropertyName == nameof(Core.Timeline.MainSegmentLength) ||
+ e.PropertyName == nameof(Core.Timeline.EndSegmentLength))
+ Update();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
@@ -88,9 +91,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e)
{
if (e.PreviousRenderProfileElement != null)
- e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
+ e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
if (e.RenderProfileElement != null)
- e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
+ e.RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update();
}
@@ -184,13 +187,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
// If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width)
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
- {
- float tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5;
- if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
- time = _profileEditorService.CurrentTime;
- else if (Math.Abs(_profileEditorService.SelectedProfileElement.StartSegmentLength.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
- time = _profileEditorService.SelectedProfileElement.StartSegmentLength;
- }
+ time = _profileEditorService.SnapToTimeline(time, TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 5), false, true);
return time;
}
@@ -351,7 +348,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
_profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
if (_profileEditorService.SelectedProfileElement != null)
- _profileEditorService.SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
+ _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
}
#endregion
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
index f90b48300..36fb2e3ad 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
@@ -246,6 +246,7 @@ namespace Artemis.UI.Screens.ProfileEditor
{
LoadWorkspaceSettings();
Module.IsProfileUpdatingDisabled = true;
+ Module.ActiveProfile?.Reset();
Module.ActiveProfileChanged += ModuleOnActiveProfileChanged;
LoadProfiles();
@@ -261,6 +262,7 @@ namespace Artemis.UI.Screens.ProfileEditor
{
SaveWorkspaceSettings();
Module.IsProfileUpdatingDisabled = false;
+ Module.ActiveProfile?.Reset();
Module.ActiveProfileChanged -= ModuleOnActiveProfileChanged;
_profileEditorService.ChangeSelectedProfile(null);
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
index ddebeb6b9..977051f10 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
@@ -125,25 +125,22 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
return;
}
- Execute.PostToUIThread(() =>
+ Rect bounds = _layerEditorService.GetLayerBounds(Layer);
+ Geometry shapeGeometry = Geometry.Empty;
+ switch (Layer.LayerShape)
{
- Rect bounds = _layerEditorService.GetLayerBounds(Layer);
- Geometry shapeGeometry = Geometry.Empty;
- switch (Layer.LayerShape)
- {
- case EllipseShape _:
- shapeGeometry = new EllipseGeometry(bounds);
- break;
- case RectangleShape _:
- shapeGeometry = new RectangleGeometry(bounds);
- break;
- }
+ case EllipseShape _:
+ shapeGeometry = new EllipseGeometry(bounds);
+ break;
+ case RectangleShape _:
+ shapeGeometry = new RectangleGeometry(bounds);
+ break;
+ }
- if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation)
- shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer);
+ if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation)
+ shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer);
- ShapeGeometry = shapeGeometry;
- });
+ ShapeGeometry = shapeGeometry;
}
private void CreateViewportRectangle()
diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml
index 7cd6ac604..c4d5e45bb 100644
--- a/src/Artemis.UI/Screens/RootView.xaml
+++ b/src/Artemis.UI/Screens/RootView.xaml
@@ -55,9 +55,9 @@
DialogTheme="Inherit"
SnackbarMessageQueue="{Binding MainMessageQueue}">
-
+
-
+
+
+
+
+
+
+
+
+
+
+ []
+
+
+
+
+
+
List item []
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs
index 2446f2666..8d7e501ac 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs
@@ -99,7 +99,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
lock (MainDataModel)
{
- MainDataModel.Update(_dataModelUIService);
+ MainDataModel.Update(_dataModelUIService, null);
}
}
diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml
index a2d0b5182..9da80b24c 100644
--- a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml
+++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml
@@ -21,7 +21,7 @@
True
True
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file