mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'feature/events' into master
This commit is contained in:
commit
143dea4c4c
@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cconditions_005Cwrappers/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cconditional/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cconditional/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cdirect/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cdirect/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.Core.DefaultTypes
|
||||||
|
{
|
||||||
|
internal class SKColorDesaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||||
|
{
|
||||||
|
public override string Name => "Desaturate";
|
||||||
|
public override string Icon => "ImageMinus";
|
||||||
|
public override string Description => "Desaturates the color by the amount in percent";
|
||||||
|
|
||||||
|
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||||
|
{
|
||||||
|
// TODO: Not so straightforward ^^
|
||||||
|
currentValue.ToHsl(out float h, out float s, out float l);
|
||||||
|
s *= (parameterValue * -1 + 100f) / 100f;
|
||||||
|
return SKColor.FromHsl(h, s, l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.Core.DefaultTypes
|
||||||
|
{
|
||||||
|
internal class SKColorSaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||||
|
{
|
||||||
|
public override string Name => "Saturate";
|
||||||
|
public override string Icon => "ImagePlus";
|
||||||
|
public override string Description => "Saturates the color by the amount in percent";
|
||||||
|
|
||||||
|
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||||
|
{
|
||||||
|
// TODO: Not so straightforward ^^
|
||||||
|
currentValue.ToHsl(out float h, out float s, out float l);
|
||||||
|
return SKColor.FromHsl(h, parameterValue, l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
Normal file
31
src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Artemis.Core.JsonConverters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An int converter that, if required, will round float values
|
||||||
|
/// </summary>
|
||||||
|
internal class ForgivingIntConverter : JsonConverter<int>
|
||||||
|
{
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
JValue jsonValue = serializer.Deserialize<JValue>(reader);
|
||||||
|
|
||||||
|
if (jsonValue.Type == JTokenType.Float)
|
||||||
|
return (int) Math.Round(jsonValue.Value<double>());
|
||||||
|
if (jsonValue.Type == JTokenType.Integer)
|
||||||
|
return jsonValue.Value<int>();
|
||||||
|
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs an update on the model
|
/// Performs an update on the model
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deltaTime">The delta time in seconds</param>
|
/// <param name="timeline">The timeline to apply during update</param>
|
||||||
void Update(double deltaTime);
|
void Update(Timeline timeline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,37 +7,51 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type LeftSideType => typeof(TLeftSide);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type RightSideType => typeof(TRightSide);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the operator on a and b
|
/// Evaluates the operator on a and b
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="a">The parameter on the left side of the expression</param>
|
/// <param name="a">The parameter on the left side of the expression</param>
|
||||||
/// <param name="b">The parameter on the right side of the expression</param>
|
/// <param name="b">The parameter on the right side of the expression</param>
|
||||||
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||||
{
|
{
|
||||||
// TODO: Can we avoid boxing/unboxing?
|
// TODO: Can we avoid boxing/unboxing?
|
||||||
TLeftSide leftSide;
|
TLeftSide leftSide;
|
||||||
if (leftSideValue != null)
|
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
|
else
|
||||||
|
{
|
||||||
leftSide = default;
|
leftSide = default;
|
||||||
|
}
|
||||||
|
|
||||||
TRightSide rightSide;
|
TRightSide rightSide;
|
||||||
if (rightSideValue != null)
|
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
|
else
|
||||||
|
{
|
||||||
rightSide = default;
|
rightSide = default;
|
||||||
|
}
|
||||||
|
|
||||||
return Evaluate(leftSide!, rightSide!);
|
return Evaluate(leftSide!, rightSide!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type LeftSideType => typeof(TLeftSide);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type RightSideType => typeof(TRightSide);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,6 +59,14 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type LeftSideType => typeof(TLeftSide);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Always <c>null</c>, not applicable to this type of condition operator
|
||||||
|
/// </summary>
|
||||||
|
public override Type? RightSideType => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the operator on a and b
|
/// Evaluates the operator on a and b
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,19 +79,18 @@ namespace Artemis.Core
|
|||||||
// TODO: Can we avoid boxing/unboxing?
|
// TODO: Can we avoid boxing/unboxing?
|
||||||
TLeftSide leftSide;
|
TLeftSide leftSide;
|
||||||
if (leftSideValue != null)
|
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
|
else
|
||||||
|
{
|
||||||
leftSide = default;
|
leftSide = default;
|
||||||
|
}
|
||||||
|
|
||||||
return Evaluate(leftSide!);
|
return Evaluate(leftSide!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type LeftSideType => typeof(TLeftSide);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Always <c>null</c>, not applicable to this type of condition operator
|
|
||||||
/// </summary>
|
|
||||||
public override Type? RightSideType => null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -36,6 +37,8 @@ namespace Artemis.Core
|
|||||||
_children.Insert(index.Value, dataModelConditionPart);
|
_children.Insert(index.Value, dataModelConditionPart);
|
||||||
else
|
else
|
||||||
_children.Add(dataModelConditionPart);
|
_children.Add(dataModelConditionPart);
|
||||||
|
|
||||||
|
OnChildAdded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,9 +52,19 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
dataModelConditionPart.Parent = null;
|
dataModelConditionPart.Parent = null;
|
||||||
_children.Remove(dataModelConditionPart);
|
_children.Remove(dataModelConditionPart);
|
||||||
|
OnChildRemoved();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all children. You monster.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearChildren()
|
||||||
|
{
|
||||||
|
while (Children.Any())
|
||||||
|
RemoveChild(Children[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the condition part on the data model
|
/// Evaluates the condition part on the data model
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -71,7 +84,7 @@ namespace Artemis.Core
|
|||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposed the condition part
|
/// Disposed the condition part
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
@ -88,5 +101,22 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
public event EventHandler ChildAdded;
|
||||||
|
public event EventHandler ChildRemoved;
|
||||||
|
|
||||||
|
protected virtual void OnChildAdded()
|
||||||
|
{
|
||||||
|
ChildAdded?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnChildRemoved()
|
||||||
|
{
|
||||||
|
ChildRemoved?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using Artemis.Core.DataModelExpansions;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
using Newtonsoft.Json;
|
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
|
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||||
/// static value
|
/// static value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelConditionPredicate : DataModelConditionPart
|
public abstract class DataModelConditionPredicate : DataModelConditionPart
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
|
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parent"></param>
|
/// <param name="parent"></param>
|
||||||
/// <param name="predicateType"></param>
|
/// <param name="predicateType"></param>
|
||||||
public DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
/// <param name="entity">A new empty entity</param>
|
||||||
|
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
|
Entity = entity;
|
||||||
PredicateType = predicateType;
|
PredicateType = predicateType;
|
||||||
Entity = new DataModelConditionPredicateEntity();
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
|
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
|
||||||
@ -32,8 +29,6 @@ namespace Artemis.Core
|
|||||||
Parent = parent;
|
Parent = parent;
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,31 +39,117 @@ namespace Artemis.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the operator
|
/// Gets the operator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseConditionOperator? Operator { get; private set; }
|
public BaseConditionOperator? Operator { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path of the left property
|
/// Gets the path of the left property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModelPath? LeftPath { get; private set; }
|
public DataModelPath? LeftPath { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path of the right property
|
/// Gets the path of the right property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModelPath? RightPath { get; private set; }
|
public DataModelPath? RightPath { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
||||||
/// <see cref="ProfileRightSideType.Static" />
|
/// <see cref="ProfileRightSideType.Static" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object? RightStaticValue { get; private set; }
|
public object? RightStaticValue { get; protected set; }
|
||||||
|
|
||||||
internal DataModelConditionPredicateEntity Entity { get; set; }
|
internal DataModelConditionPredicateEntity Entity { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||||
|
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
|
||||||
|
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
||||||
|
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||||
|
|
||||||
|
LeftPath?.Dispose();
|
||||||
|
RightPath?.Dispose();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Initialization
|
||||||
|
|
||||||
|
internal void Initialize()
|
||||||
|
{
|
||||||
|
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
|
||||||
|
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||||
|
|
||||||
|
InitializeLeftPath();
|
||||||
|
|
||||||
|
// Operator
|
||||||
|
if (Entity.OperatorPluginGuid != null)
|
||||||
|
{
|
||||||
|
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||||
|
if (conditionOperator != null)
|
||||||
|
UpdateOperator(conditionOperator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right side dynamic
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||||
|
InitializeRightPath();
|
||||||
|
// Right side static
|
||||||
|
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (LeftPath != null && LeftPath.IsValid)
|
||||||
|
{
|
||||||
|
// Use the left side type so JSON.NET has a better idea what to do
|
||||||
|
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||||
|
object? rightSideValue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||||
|
}
|
||||||
|
// If deserialization fails, use the type's default
|
||||||
|
catch (JsonSerializationException e)
|
||||||
|
{
|
||||||
|
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||||
|
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateRightSideStatic(rightSideValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Hope for the best...
|
||||||
|
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonReaderException e)
|
||||||
|
{
|
||||||
|
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void InitializeLeftPath();
|
||||||
|
protected abstract void InitializeRightPath();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Modification
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the left side of the predicate
|
/// Updates the left side of the predicate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path pointing to the left side value inside the data model</param>
|
/// <param name="path">The path pointing to the left side value inside the data model</param>
|
||||||
public void UpdateLeftSide(DataModelPath? path)
|
public virtual void UpdateLeftSide(DataModelPath? path)
|
||||||
{
|
{
|
||||||
if (path != null && !path.IsValid)
|
if (path != null && !path.IsValid)
|
||||||
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
|
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
|
||||||
@ -113,6 +194,7 @@ namespace Artemis.Core
|
|||||||
RightStaticValue = staticValue;
|
RightStaticValue = staticValue;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the operator does not support a right side, always set it to null
|
// If the operator does not support a right side, always set it to null
|
||||||
if (Operator.RightSideType == 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 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;
|
RightStaticValue = staticValue;
|
||||||
else if (staticValue != null)
|
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
|
// If null create a default instance for value types or simply make it null for reference types
|
||||||
else if (Operator.RightSideType.IsValueType)
|
else if (preferredType.IsValueType)
|
||||||
RightStaticValue = Activator.CreateInstance(Operator.RightSideType);
|
RightStaticValue = Activator.CreateInstance(preferredType);
|
||||||
else
|
else
|
||||||
RightStaticValue = null;
|
RightStaticValue = null;
|
||||||
}
|
}
|
||||||
@ -161,137 +244,10 @@ namespace Artemis.Core
|
|||||||
ValidateRightSide();
|
ValidateRightSide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override bool Evaluate()
|
/// Determines the best type to use for the right side op this predicate
|
||||||
{
|
/// </summary>
|
||||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
public abstract Type? GetPreferredRightSideType();
|
||||||
return false;
|
|
||||||
|
|
||||||
// Compare with a static value
|
|
||||||
if (PredicateType == ProfileRightSideType.Static)
|
|
||||||
{
|
|
||||||
object? leftSideValue = LeftPath.GetValue();
|
|
||||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RightPath == null || !RightPath.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Compare with dynamic values
|
|
||||||
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
|
||||||
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
|
|
||||||
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
|
||||||
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
|
||||||
|
|
||||||
LeftPath?.Dispose();
|
|
||||||
RightPath?.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
internal override bool EvaluateObject(object target)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Save()
|
|
||||||
{
|
|
||||||
// Don't save an invalid state
|
|
||||||
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Entity.PredicateType = (int) PredicateType;
|
|
||||||
|
|
||||||
LeftPath?.Save();
|
|
||||||
Entity.LeftPath = LeftPath?.Entity;
|
|
||||||
RightPath?.Save();
|
|
||||||
Entity.RightPath = RightPath?.Entity;
|
|
||||||
|
|
||||||
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
|
||||||
|
|
||||||
if (Operator != null)
|
|
||||||
{
|
|
||||||
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
|
|
||||||
Entity.OperatorType = Operator.GetType().Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Initialize()
|
|
||||||
{
|
|
||||||
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
|
|
||||||
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
|
|
||||||
|
|
||||||
// Left side
|
|
||||||
if (Entity.LeftPath != null)
|
|
||||||
LeftPath = new DataModelPath(null, Entity.LeftPath);
|
|
||||||
|
|
||||||
// Operator
|
|
||||||
if (Entity.OperatorPluginGuid != null)
|
|
||||||
{
|
|
||||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
|
||||||
if (conditionOperator != null)
|
|
||||||
UpdateOperator(conditionOperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right side dynamic
|
|
||||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
|
||||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
|
||||||
// Right side static
|
|
||||||
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (LeftPath != null && LeftPath.IsValid)
|
|
||||||
{
|
|
||||||
// Use the left side type so JSON.NET has a better idea what to do
|
|
||||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
|
||||||
object? rightSideValue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
|
||||||
}
|
|
||||||
// If deserialization fails, use the type's default
|
|
||||||
catch (JsonSerializationException e)
|
|
||||||
{
|
|
||||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
|
||||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateRightSideStatic(rightSideValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Hope for the best...
|
|
||||||
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (JsonReaderException)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
// TODO: Some logging would be nice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override DataModelConditionPartEntity GetEntity()
|
|
||||||
{
|
|
||||||
return Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateOperator()
|
private void ValidateOperator()
|
||||||
{
|
{
|
||||||
@ -327,6 +283,76 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Evaluation
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Evaluate()
|
||||||
|
{
|
||||||
|
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the operator does not support a right side, immediately evaluate with null
|
||||||
|
if (Operator.RightSideType == null)
|
||||||
|
return Operator.InternalEvaluate(LeftPath.GetValue(), null);
|
||||||
|
|
||||||
|
// Compare with a static value
|
||||||
|
if (PredicateType == ProfileRightSideType.Static)
|
||||||
|
{
|
||||||
|
object? leftSideValue = LeftPath.GetValue();
|
||||||
|
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RightPath == null || !RightPath.IsValid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Compare with dynamic values
|
||||||
|
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
internal override bool EvaluateObject(object target)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Storage
|
||||||
|
|
||||||
|
internal override DataModelConditionPartEntity GetEntity()
|
||||||
|
{
|
||||||
|
return Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Save()
|
||||||
|
{
|
||||||
|
// Don't save an invalid state
|
||||||
|
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Entity.PredicateType = (int) PredicateType;
|
||||||
|
|
||||||
|
LeftPath?.Save();
|
||||||
|
Entity.LeftPath = LeftPath?.Entity;
|
||||||
|
RightPath?.Save();
|
||||||
|
Entity.RightPath = RightPath?.Entity;
|
||||||
|
|
||||||
|
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||||
|
|
||||||
|
if (Operator != null)
|
||||||
|
{
|
||||||
|
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
|
||||||
|
Entity.OperatorType = Operator.GetType().Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Event handlers
|
#region Event handlers
|
||||||
|
|
||||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
||||||
@ -0,0 +1,241 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A condition that evaluates to true when an event is triggered
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelConditionEvent : DataModelConditionPart
|
||||||
|
{
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _reinitializing;
|
||||||
|
private IDataModelEvent? _event;
|
||||||
|
private bool _eventTriggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
public DataModelConditionEvent(DataModelConditionPart parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
Entity = new DataModelConditionEventEntity();
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
Entity = entity;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path of the event property
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPath? EventPath { get; private set; }
|
||||||
|
|
||||||
|
public Type? EventArgumentType { get; set; }
|
||||||
|
|
||||||
|
internal DataModelConditionEventEntity Entity { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Evaluate()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||||
|
|
||||||
|
// Ensure the event has not been replaced
|
||||||
|
if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent)
|
||||||
|
SubscribeToDataModelEvent(dataModelEvent);
|
||||||
|
|
||||||
|
// Only evaluate to true once every time the event has been triggered
|
||||||
|
if (!_eventTriggered)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_eventTriggered = false;
|
||||||
|
|
||||||
|
// If there is a child (root group), it must evaluate to true whenever the event triggered
|
||||||
|
if (Children.Any())
|
||||||
|
return Children[0].EvaluateObject(_event.LastEventArgumentsUntyped);
|
||||||
|
|
||||||
|
// If there are no children, we always evaluate to true whenever the event triggered
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
|
||||||
|
{
|
||||||
|
if (_event != null)
|
||||||
|
_event.EventTriggered -= OnEventTriggered;
|
||||||
|
|
||||||
|
_event = dataModelEvent;
|
||||||
|
if (_event != null)
|
||||||
|
_event.EventTriggered += OnEventTriggered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the event the condition is triggered by
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateEvent(DataModelPath? path)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||||
|
|
||||||
|
if (path != null && !path.IsValid)
|
||||||
|
throw new ArtemisCoreException("Cannot update event to an invalid path");
|
||||||
|
|
||||||
|
EventPath?.Dispose();
|
||||||
|
EventPath = path != null ? new DataModelPath(path) : null;
|
||||||
|
SubscribeToEventPath();
|
||||||
|
|
||||||
|
// Remove the old root group that was tied to the old data model
|
||||||
|
ClearChildren();
|
||||||
|
|
||||||
|
if (EventPath != null)
|
||||||
|
{
|
||||||
|
EventArgumentType = GetEventArgumentType();
|
||||||
|
// Create a new root group
|
||||||
|
AddChild(new DataModelConditionGroup(this));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EventArgumentType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type? GetEventArgumentType()
|
||||||
|
{
|
||||||
|
if (EventPath == null || !EventPath.IsValid)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Cannot rely on EventPath.GetValue() because part of the path might be null
|
||||||
|
Type eventType = EventPath.GetPropertyType()!;
|
||||||
|
return eventType.IsGenericType ? eventType.GetGenericArguments()[0] : typeof(DataModelEventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
EventPath?.Dispose();
|
||||||
|
|
||||||
|
foreach (DataModelConditionPart child in Children)
|
||||||
|
child.Dispose();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
internal override bool EvaluateObject(object target)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Save()
|
||||||
|
{
|
||||||
|
// Don't save an invalid state
|
||||||
|
if (EventPath != null && !EventPath.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Target list
|
||||||
|
EventPath?.Save();
|
||||||
|
Entity.EventPath = EventPath?.Entity;
|
||||||
|
|
||||||
|
// Children
|
||||||
|
Entity.Children.Clear();
|
||||||
|
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||||
|
foreach (DataModelConditionPart child in Children)
|
||||||
|
child.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override DataModelConditionPartEntity GetEntity()
|
||||||
|
{
|
||||||
|
return Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Initialize()
|
||||||
|
{
|
||||||
|
ClearChildren();
|
||||||
|
|
||||||
|
if (Entity.EventPath == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ensure the list path is valid and points to a list
|
||||||
|
DataModelPath eventPath = new DataModelPath(null, Entity.EventPath);
|
||||||
|
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
|
||||||
|
if (eventPath.IsValid && !PointsToEvent(eventPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventPath = eventPath;
|
||||||
|
SubscribeToEventPath();
|
||||||
|
|
||||||
|
EventArgumentType = GetEventArgumentType();
|
||||||
|
// There should only be one child and it should be a group
|
||||||
|
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
|
||||||
|
{
|
||||||
|
AddChild(new DataModelConditionGroup(this, rootGroup));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entity.Children.Clear();
|
||||||
|
AddChild(new DataModelConditionGroup(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PointsToEvent(DataModelPath dataModelPath)
|
||||||
|
{
|
||||||
|
Type? type = dataModelPath.GetPropertyType();
|
||||||
|
if (type == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return typeof(IDataModelEvent).IsAssignableFrom(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeToEventPath()
|
||||||
|
{
|
||||||
|
if (EventPath == null) return;
|
||||||
|
EventPath.PathValidated += EventPathOnPathValidated;
|
||||||
|
EventPath.PathInvalidated += EventPathOnPathInvalidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Event handlers
|
||||||
|
|
||||||
|
private void OnEventTriggered(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_eventTriggered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EventPathOnPathValidated(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_reinitializing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_reinitializing = true;
|
||||||
|
EventPath?.Dispose();
|
||||||
|
Initialize();
|
||||||
|
_reinitializing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EventPathOnPathInvalidated(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_reinitializing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_reinitializing = true;
|
||||||
|
EventPath?.Dispose();
|
||||||
|
Initialize();
|
||||||
|
_reinitializing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A predicate like evaluated inside a <see cref="DataModelConditionEvent" />
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelConditionEventPredicate : DataModelConditionPredicate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelConditionEventPredicate" /> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
/// <param name="predicateType"></param>
|
||||||
|
public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||||
|
: base(parent, predicateType, new DataModelConditionEventPredicateEntity())
|
||||||
|
{
|
||||||
|
DataModelConditionEvent = null!;
|
||||||
|
ApplyParentEvent();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity)
|
||||||
|
: base(parent, entity)
|
||||||
|
{
|
||||||
|
DataModelConditionEvent = null!;
|
||||||
|
ApplyParentEvent();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data model condition event this predicate belongs to
|
||||||
|
/// </summary>
|
||||||
|
public DataModelConditionEvent DataModelConditionEvent { get; private set; }
|
||||||
|
|
||||||
|
private void ApplyParentEvent()
|
||||||
|
{
|
||||||
|
DataModelConditionPart? current = Parent;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
if (current is DataModelConditionEvent parentEvent)
|
||||||
|
{
|
||||||
|
DataModelConditionEvent = parentEvent;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DataModelConditionEvent == null)
|
||||||
|
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event");
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? GetEventPathValue(DataModelPath path, object target)
|
||||||
|
{
|
||||||
|
lock (path)
|
||||||
|
{
|
||||||
|
if (!(path.Target is EventPredicateWrapperDataModel wrapper))
|
||||||
|
throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target");
|
||||||
|
|
||||||
|
wrapper.UntypedArguments = target;
|
||||||
|
return path.GetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Initialization
|
||||||
|
|
||||||
|
protected override void InitializeLeftPath()
|
||||||
|
{
|
||||||
|
if (Entity.LeftPath != null)
|
||||||
|
LeftPath = DataModelConditionEvent.EventArgumentType != null
|
||||||
|
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeRightPath()
|
||||||
|
{
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||||
|
{
|
||||||
|
// Right side dynamic using event arguments
|
||||||
|
if (Entity.RightPath.WrapperType == PathWrapperType.Event)
|
||||||
|
{
|
||||||
|
RightPath = DataModelConditionEvent.EventArgumentType != null
|
||||||
|
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
// Right side dynamic
|
||||||
|
else
|
||||||
|
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Modification
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type? GetPreferredRightSideType()
|
||||||
|
{
|
||||||
|
Type? preferredType = Operator?.RightSideType;
|
||||||
|
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||||
|
if (preferredType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||||
|
preferredType = leftSideType;
|
||||||
|
|
||||||
|
return preferredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Evaluation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not supported for event predicates, always returns <c>false</c>
|
||||||
|
/// </summary>
|
||||||
|
public override bool Evaluate()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override bool EvaluateObject(object target)
|
||||||
|
{
|
||||||
|
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the operator does not support a right side, immediately evaluate with null
|
||||||
|
if (Operator.RightSideType == null)
|
||||||
|
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null);
|
||||||
|
|
||||||
|
// Compare with a static value
|
||||||
|
if (PredicateType == ProfileRightSideType.Static)
|
||||||
|
{
|
||||||
|
object? leftSideValue = GetEventPathValue(LeftPath, target);
|
||||||
|
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RightPath == null || !RightPath.IsValid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Compare with dynamic values
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||||
|
{
|
||||||
|
// If the path targets a property inside the event, evaluate on the event path value instead of the right path value
|
||||||
|
if (RightPath.Target is EventPredicateWrapperDataModel)
|
||||||
|
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target));
|
||||||
|
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||||
|
/// static value
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelConditionGeneralPredicate" /> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
/// <param name="predicateType"></param>
|
||||||
|
public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||||
|
: base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
|
||||||
|
: base(parent, entity)
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Modification
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type? GetPreferredRightSideType()
|
||||||
|
{
|
||||||
|
Type? preferredType = Operator?.RightSideType;
|
||||||
|
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||||
|
if (preferredType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||||
|
preferredType = leftSideType;
|
||||||
|
|
||||||
|
return preferredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Initialization
|
||||||
|
|
||||||
|
protected override void InitializeLeftPath()
|
||||||
|
{
|
||||||
|
if (Entity.LeftPath != null)
|
||||||
|
LeftPath = new DataModelPath(null, Entity.LeftPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeRightPath()
|
||||||
|
{
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||||
|
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
@ -21,6 +23,8 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
Entity = new DataModelConditionGroupEntity();
|
Entity = new DataModelConditionGroupEntity();
|
||||||
|
ChildAdded += OnChildrenChanged;
|
||||||
|
ChildRemoved += OnChildrenChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -40,11 +44,19 @@ namespace Artemis.Core
|
|||||||
AddChild(new DataModelConditionGroup(this, groupEntity));
|
AddChild(new DataModelConditionGroup(this, groupEntity));
|
||||||
else if (childEntity is DataModelConditionListEntity listEntity)
|
else if (childEntity is DataModelConditionListEntity listEntity)
|
||||||
AddChild(new DataModelConditionList(this, listEntity));
|
AddChild(new DataModelConditionList(this, listEntity));
|
||||||
else if (childEntity is DataModelConditionPredicateEntity predicateEntity)
|
else if (childEntity is DataModelConditionEventEntity eventEntity)
|
||||||
AddChild(new DataModelConditionPredicate(this, predicateEntity));
|
AddChild(new DataModelConditionEvent(this, eventEntity));
|
||||||
|
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
|
||||||
|
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
|
||||||
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
|
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
|
||||||
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
|
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
|
||||||
|
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
|
||||||
|
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||||
|
ChildAdded += OnChildrenChanged;
|
||||||
|
ChildRemoved += OnChildrenChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -52,6 +64,11 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public BooleanOperator BooleanOperator { get; set; }
|
public BooleanOperator BooleanOperator { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this group contains any events
|
||||||
|
/// </summary>
|
||||||
|
public bool ContainsEvents { get; private set; }
|
||||||
|
|
||||||
internal DataModelConditionGroupEntity Entity { get; set; }
|
internal DataModelConditionGroupEntity Entity { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -67,16 +84,26 @@ namespace Artemis.Core
|
|||||||
if (Children.Count == 1)
|
if (Children.Count == 1)
|
||||||
return Children[0].Evaluate();
|
return Children[0].Evaluate();
|
||||||
|
|
||||||
|
if (ContainsEvents)
|
||||||
|
{
|
||||||
|
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
|
||||||
|
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
|
||||||
|
}
|
||||||
|
return EvaluateWithOperator(Children);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
|
||||||
|
{
|
||||||
switch (BooleanOperator)
|
switch (BooleanOperator)
|
||||||
{
|
{
|
||||||
case BooleanOperator.And:
|
case BooleanOperator.And:
|
||||||
return Children.All(c => c.Evaluate());
|
return targets.All(c => c.Evaluate());
|
||||||
case BooleanOperator.Or:
|
case BooleanOperator.Or:
|
||||||
return Children.Any(c => c.Evaluate());
|
return targets.Any(c => c.Evaluate());
|
||||||
case BooleanOperator.AndNot:
|
case BooleanOperator.AndNot:
|
||||||
return Children.All(c => !c.Evaluate());
|
return targets.All(c => !c.Evaluate());
|
||||||
case BooleanOperator.OrNot:
|
case BooleanOperator.OrNot:
|
||||||
return Children.Any(c => !c.Evaluate());
|
return targets.Any(c => !c.Evaluate());
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
@ -133,6 +160,11 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
return Entity;
|
return Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnChildrenChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -180,7 +180,7 @@ namespace Artemis.Core
|
|||||||
DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
|
DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
|
||||||
Type listType = listPath.GetPropertyType()!;
|
Type listType = listPath.GetPropertyType()!;
|
||||||
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
|
// 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;
|
return;
|
||||||
|
|
||||||
ListPath = listPath;
|
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()
|
private void SubscribeToListPath()
|
||||||
{
|
{
|
||||||
if (ListPath == null) return;
|
if (ListPath == null) return;
|
||||||
|
|||||||
@ -1,16 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Core.DataModelExpansions;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
|
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelConditionListPredicate : DataModelConditionPart
|
public class DataModelConditionListPredicate : DataModelConditionPredicate
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
|
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
|
||||||
@ -18,138 +15,104 @@ namespace Artemis.Core
|
|||||||
/// <param name="parent"></param>
|
/// <param name="parent"></param>
|
||||||
/// <param name="predicateType"></param>
|
/// <param name="predicateType"></param>
|
||||||
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||||
|
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
|
||||||
{
|
{
|
||||||
Parent = parent;
|
|
||||||
PredicateType = predicateType;
|
|
||||||
Entity = new DataModelConditionListPredicateEntity();
|
|
||||||
|
|
||||||
DataModelConditionList = null!;
|
DataModelConditionList = null!;
|
||||||
ApplyParentList();
|
ApplyParentList();
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
|
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
|
||||||
|
: base(parent, entity)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
|
||||||
Entity = entity;
|
|
||||||
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
|
||||||
|
|
||||||
DataModelConditionList = null!;
|
DataModelConditionList = null!;
|
||||||
ApplyParentList();
|
ApplyParentList();
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the predicate type
|
|
||||||
/// </summary>
|
|
||||||
public ProfileRightSideType PredicateType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the operator
|
|
||||||
/// </summary>
|
|
||||||
public BaseConditionOperator? Operator { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data model condition list this predicate belongs to
|
/// Gets the data model condition list this predicate belongs to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModelConditionList DataModelConditionList { get; private set; }
|
public DataModelConditionList DataModelConditionList { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
private void ApplyParentList()
|
||||||
/// Gets the path of the left property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? LeftPath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the right property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? RightPath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
|
||||||
/// <see cref="ProfileRightSideType.Static" />
|
|
||||||
/// </summary>
|
|
||||||
public object? RightStaticValue { get; private set; }
|
|
||||||
|
|
||||||
internal DataModelConditionListPredicateEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the left side of the predicate
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path pointing to the left side value inside the list</param>
|
|
||||||
public void UpdateLeftSide(DataModelPath? path)
|
|
||||||
{
|
{
|
||||||
if (DataModelConditionList.IsPrimitiveList)
|
DataModelConditionPart? current = Parent;
|
||||||
throw new ArtemisCoreException("Cannot apply a left side to a predicate inside a primitive list");
|
while (current != null)
|
||||||
|
|
||||||
if (path != null && !path.IsValid)
|
|
||||||
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
|
|
||||||
|
|
||||||
LeftPath?.Dispose();
|
|
||||||
LeftPath = path != null ? new DataModelPath(path) : null;
|
|
||||||
|
|
||||||
ValidateOperator();
|
|
||||||
ValidateRightSide();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the right side of the predicate using a path and makes the predicate dynamic
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path pointing to the right side value</param>
|
|
||||||
public void UpdateRightSideDynamic(DataModelPath? path)
|
|
||||||
{
|
|
||||||
if (path != null && !path.IsValid)
|
|
||||||
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
|
|
||||||
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
|
|
||||||
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
|
|
||||||
|
|
||||||
RightPath?.Dispose();
|
|
||||||
RightPath = path != null ? new DataModelPath(path) : null;
|
|
||||||
|
|
||||||
PredicateType = ProfileRightSideType.Dynamic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="staticValue">The right side value to use</param>
|
|
||||||
public void UpdateRightSideStatic(object? staticValue)
|
|
||||||
{
|
|
||||||
PredicateType = ProfileRightSideType.Static;
|
|
||||||
RightPath?.Dispose();
|
|
||||||
RightPath = null;
|
|
||||||
|
|
||||||
SetStaticValue(staticValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the operator of the predicate and re-compiles the expression
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="conditionOperator"></param>
|
|
||||||
public void UpdateOperator(BaseConditionOperator? conditionOperator)
|
|
||||||
{
|
|
||||||
if (conditionOperator == null)
|
|
||||||
{
|
{
|
||||||
Operator = null;
|
if (current is DataModelConditionList parentList)
|
||||||
return;
|
{
|
||||||
|
DataModelConditionList = parentList;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to clear compiled expressions, without a left data model they are already null
|
if (DataModelConditionList == null)
|
||||||
if (LeftPath == null || !LeftPath.IsValid)
|
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object? GetListPathValue(DataModelPath path, object target)
|
||||||
|
{
|
||||||
|
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
|
||||||
|
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
|
||||||
|
|
||||||
|
wrapper.UntypedValue = target;
|
||||||
|
return path.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Initialization
|
||||||
|
|
||||||
|
protected override void InitializeLeftPath()
|
||||||
|
{
|
||||||
|
if (Entity.LeftPath != null)
|
||||||
|
LeftPath = DataModelConditionList.ListType != null
|
||||||
|
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeRightPath()
|
||||||
|
{
|
||||||
|
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||||
|
{
|
||||||
|
// Right side dynamic inside the list
|
||||||
|
if (Entity.RightPath.WrapperType == PathWrapperType.List)
|
||||||
|
{
|
||||||
|
RightPath = DataModelConditionList.ListType != null
|
||||||
|
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
// Right side dynamic
|
||||||
|
else
|
||||||
|
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Modification
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Type? GetPreferredRightSideType()
|
||||||
|
{
|
||||||
|
Type? preferredType = Operator?.RightSideType;
|
||||||
|
Type? leftSideType = DataModelConditionList.IsPrimitiveList
|
||||||
|
? DataModelConditionList.ListType
|
||||||
|
: LeftPath?.GetPropertyType();
|
||||||
|
if (preferredType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||||
|
preferredType = leftSideType;
|
||||||
|
|
||||||
|
return preferredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Evaluation
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not supported for list predicates, always returns <c>false</c>
|
/// Not supported for list predicates, always returns <c>false</c>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -158,45 +121,6 @@ namespace Artemis.Core
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the provided path is contained inside the list
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path to evaluate</param>
|
|
||||||
public bool ListContainsInnerPath(string path)
|
|
||||||
{
|
|
||||||
if (DataModelConditionList.ListType == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
string[] parts = path.Split('.');
|
|
||||||
Type current = DataModelConditionList.ListType;
|
|
||||||
foreach (string part in parts)
|
|
||||||
{
|
|
||||||
PropertyInfo? property = current.GetProperty(part);
|
|
||||||
current = property?.PropertyType;
|
|
||||||
|
|
||||||
if (property == null)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
|
||||||
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
|
||||||
|
|
||||||
LeftPath?.Dispose();
|
|
||||||
RightPath?.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal override bool EvaluateObject(object target)
|
internal override bool EvaluateObject(object target)
|
||||||
{
|
{
|
||||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||||
@ -227,226 +151,6 @@ namespace Artemis.Core
|
|||||||
return false;
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
internal class EventPredicateWrapperDataModel<T> : EventPredicateWrapperDataModel
|
||||||
|
{
|
||||||
|
[DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")]
|
||||||
|
public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a datamodel that wraps the event arguments of an event
|
||||||
|
/// </summary>
|
||||||
|
public abstract class EventPredicateWrapperDataModel : DataModel
|
||||||
|
{
|
||||||
|
internal EventPredicateWrapperDataModel()
|
||||||
|
{
|
||||||
|
PluginInfo = Constants.CorePluginInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataModelIgnore]
|
||||||
|
public object? UntypedArguments { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="EventPredicateWrapperDataModel"/> class
|
||||||
|
/// </summary>
|
||||||
|
public static EventPredicateWrapperDataModel Create(Type type)
|
||||||
|
{
|
||||||
|
object? instance = Activator.CreateInstance(typeof(EventPredicateWrapperDataModel<>).MakeGenericType(type));
|
||||||
|
if (instance == null)
|
||||||
|
throw new ArtemisCoreException($"Failed to create an instance of EventPredicateWrapperDataModel<T> for type {type.Name}");
|
||||||
|
|
||||||
|
return (EventPredicateWrapperDataModel) instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,9 @@ namespace Artemis.Core
|
|||||||
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
|
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a datamodel that wraps a value in a list
|
||||||
|
/// </summary>
|
||||||
public abstract class ListPredicateWrapperDataModel : DataModel
|
public abstract class ListPredicateWrapperDataModel : DataModel
|
||||||
{
|
{
|
||||||
internal ListPredicateWrapperDataModel()
|
internal ListPredicateWrapperDataModel()
|
||||||
@ -19,6 +22,9 @@ namespace Artemis.Core
|
|||||||
[DataModelIgnore]
|
[DataModelIgnore]
|
||||||
public object? UntypedValue { get; set; }
|
public object? UntypedValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel"/> class
|
||||||
|
/// </summary>
|
||||||
public static ListPredicateWrapperDataModel Create(Type type)
|
public static ListPredicateWrapperDataModel Create(Type type)
|
||||||
{
|
{
|
||||||
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
|
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
|
||||||
@ -60,9 +60,9 @@ namespace Artemis.Core
|
|||||||
/// Gets ors ets the easing function of the data binding
|
/// Gets ors ets the easing function of the data binding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Easings.Functions EasingFunction { get; set; }
|
public Easings.Functions EasingFunction { get; set; }
|
||||||
|
|
||||||
internal DataBindingEntity Entity { get; }
|
internal DataBindingEntity Entity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current value of the data binding
|
/// Gets the current value of the data binding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -137,16 +137,26 @@ namespace Artemis.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the smoothing progress of the data binding
|
/// Updates the smoothing progress of the data binding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
|
/// <param name="timeline">The timeline to apply during update</param>
|
||||||
public void Update(double deltaTime)
|
public void Update(Timeline timeline)
|
||||||
|
{
|
||||||
|
UpdateWithDelta(timeline.Delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the smoothing progress of the data binding
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delta">The delta to apply during update</param>
|
||||||
|
public void UpdateWithDelta(TimeSpan delta)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("DataBinding");
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
|
||||||
// Data bindings cannot go back in time like brushes
|
// 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)
|
if (_easingProgress > EasingTime)
|
||||||
_easingProgress = EasingTime;
|
_easingProgress = EasingTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,7 +75,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes a condition from the conditional data binding's <see cref="Conditions" /> collection and disposes it
|
/// Removes a condition from the conditional data binding's <see cref="Conditions" /> collection and disposes it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -200,7 +200,7 @@ namespace Artemis.Core
|
|||||||
ParameterPath = new DataModelPath(null, Entity.ParameterPath);
|
ParameterPath = new DataModelPath(null, Entity.ParameterPath);
|
||||||
}
|
}
|
||||||
// Static parameter
|
// 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
|
// Use the target type so JSON.NET has a better idea what to do
|
||||||
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
|
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
|
||||||
|
|||||||
225
src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
Normal file
225
src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a data model event with event arguments of type <typeparamref name="T" />
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelEvent<T> : IDataModelEvent where T : DataModelEventArgs
|
||||||
|
{
|
||||||
|
private bool _trackHistory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelEvent{T}" /> class with history tracking disabled
|
||||||
|
/// </summary>
|
||||||
|
public DataModelEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelEvent{T}" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||||
|
public DataModelEvent(bool trackHistory)
|
||||||
|
{
|
||||||
|
_trackHistory = trackHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
|
||||||
|
public DateTime LastTrigger { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event arguments of the last time the event was triggered
|
||||||
|
/// </summary>
|
||||||
|
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||||
|
public T? LastEventArguments { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||||
|
public int TriggerCount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a queue of the last 20 event arguments
|
||||||
|
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||||
|
/// </summary>
|
||||||
|
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||||
|
public Queue<T> EventArgumentsHistory { get; } = new Queue<T>(20);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger the event with the given <paramref name="eventArgs" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs">The event argument to pass to the event</param>
|
||||||
|
public void Trigger(T eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs == null) throw new ArgumentNullException(nameof(eventArgs));
|
||||||
|
eventArgs.TriggerTime = DateTime.Now;
|
||||||
|
|
||||||
|
LastEventArguments = eventArgs;
|
||||||
|
LastTrigger = DateTime.Now;
|
||||||
|
TriggerCount++;
|
||||||
|
|
||||||
|
if (TrackHistory)
|
||||||
|
{
|
||||||
|
lock (EventArgumentsHistory)
|
||||||
|
{
|
||||||
|
if (EventArgumentsHistory.Count == 20)
|
||||||
|
EventArgumentsHistory.Dequeue();
|
||||||
|
EventArgumentsHistory.Enqueue(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnEventTriggered();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual void OnEventTriggered()
|
||||||
|
{
|
||||||
|
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public Type ArgumentsType => typeof(T);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public bool TrackHistory
|
||||||
|
{
|
||||||
|
get => _trackHistory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
EventArgumentsHistory.Clear();
|
||||||
|
_trackHistory = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.Cast<DataModelEventArgs>().ToList();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler? EventTriggered;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
TriggerCount = 0;
|
||||||
|
EventArgumentsHistory.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a data model event without event arguments
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelEvent : IDataModelEvent
|
||||||
|
{
|
||||||
|
private bool _trackHistory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelEvent" /> class with history tracking disabled
|
||||||
|
/// </summary>
|
||||||
|
public DataModelEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DataModelEvent" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trackHistory">A boolean indicating whether the last 20 events should be tracked</param>
|
||||||
|
public DataModelEvent(bool trackHistory)
|
||||||
|
{
|
||||||
|
_trackHistory = trackHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
|
||||||
|
public DateTime LastTrigger { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event arguments of the last time the event was triggered
|
||||||
|
/// </summary>
|
||||||
|
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||||
|
public DataModelEventArgs? LastEventArguments { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
|
||||||
|
public int TriggerCount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a queue of the last 20 event arguments
|
||||||
|
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||||
|
/// </summary>
|
||||||
|
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
|
||||||
|
public Queue<DataModelEventArgs> EventArgumentsHistory { get; } = new Queue<DataModelEventArgs>(20);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger the event
|
||||||
|
/// </summary>
|
||||||
|
public void Trigger()
|
||||||
|
{
|
||||||
|
DataModelEventArgs eventArgs = new DataModelEventArgs {TriggerTime = DateTime.Now};
|
||||||
|
|
||||||
|
LastEventArguments = eventArgs;
|
||||||
|
LastTrigger = DateTime.Now;
|
||||||
|
TriggerCount++;
|
||||||
|
|
||||||
|
if (TrackHistory)
|
||||||
|
{
|
||||||
|
lock (EventArgumentsHistory)
|
||||||
|
{
|
||||||
|
if (EventArgumentsHistory.Count == 20)
|
||||||
|
EventArgumentsHistory.Dequeue();
|
||||||
|
EventArgumentsHistory.Enqueue(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnEventTriggered();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual void OnEventTriggered()
|
||||||
|
{
|
||||||
|
EventTriggered?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public Type ArgumentsType => typeof(DataModelEventArgs);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public bool TrackHistory
|
||||||
|
{
|
||||||
|
get => _trackHistory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
EventArgumentsHistory.Clear();
|
||||||
|
_trackHistory = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataModelIgnore]
|
||||||
|
public List<DataModelEventArgs> EventArgumentsHistoryUntyped => EventArgumentsHistory.ToList();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler? EventTriggered;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
TriggerCount = 0;
|
||||||
|
EventArgumentsHistory.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the base class for data model events that contain event data
|
||||||
|
/// </summary>
|
||||||
|
public class DataModelEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time at which the event with these arguments was triggered
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TriggerTime { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
@ -111,18 +110,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
|
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean indicating whether this data model path points to a list
|
|
||||||
/// </summary>
|
|
||||||
public bool PointsToList
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
Type? type = GetPropertyType();
|
|
||||||
return type?.IsGenericEnumerable() ?? false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelPathEntity Entity { get; }
|
internal DataModelPathEntity Entity { get; }
|
||||||
|
|
||||||
internal Func<object, object>? Accessor { get; private set; }
|
internal Func<object, object>? Accessor { get; private set; }
|
||||||
@ -293,6 +280,13 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
Entity.Path = Path;
|
Entity.Path = Path;
|
||||||
Entity.DataModelGuid = DataModelGuid;
|
Entity.DataModelGuid = DataModelGuid;
|
||||||
|
|
||||||
|
Entity.WrapperType = Target switch
|
||||||
|
{
|
||||||
|
ListPredicateWrapperDataModel _ => PathWrapperType.List,
|
||||||
|
EventPredicateWrapperDataModel _ => PathWrapperType.Event,
|
||||||
|
_ => PathWrapperType.None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
50
src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs
Normal file
50
src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
internal interface IDataModelEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last time the event was triggered
|
||||||
|
/// </summary>
|
||||||
|
DateTime LastTrigger { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of times the event was triggered
|
||||||
|
/// </summary>
|
||||||
|
int TriggerCount { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of arguments this event contains
|
||||||
|
/// </summary>
|
||||||
|
Type ArgumentsType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the last 20 events should be tracked
|
||||||
|
/// <para>Note: setting this to <see langword="false" /> will clear the current history</para>
|
||||||
|
/// </summary>
|
||||||
|
bool TrackHistory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event arguments of the last time the event was triggered by its base type
|
||||||
|
/// </summary>
|
||||||
|
public DataModelEventArgs? LastEventArgumentsUntyped { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of the last 20 event arguments by their base type.
|
||||||
|
/// <para>Always empty if <see cref="TrackHistory" /> is <see langword="false" /></para>
|
||||||
|
/// </summary>
|
||||||
|
public List<DataModelEventArgs> EventArgumentsHistoryUntyped { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the event is triggered
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler EventTriggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the trigger count and history of this data model event
|
||||||
|
/// </summary>
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,19 +30,16 @@ namespace Artemis.Core
|
|||||||
Profile = Parent.Profile;
|
Profile = Parent.Profile;
|
||||||
Name = name;
|
Name = name;
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
DisplayContinuously = true;
|
|
||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
|
|
||||||
ApplyRenderElementDefaults();
|
|
||||||
Parent.AddChild(this);
|
Parent.AddChild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
|
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
|
||||||
{
|
{
|
||||||
FolderEntity = folderEntity;
|
FolderEntity = folderEntity;
|
||||||
|
|
||||||
EntityId = folderEntity.Id;
|
EntityId = folderEntity.Id;
|
||||||
|
|
||||||
Profile = profile;
|
Profile = profile;
|
||||||
@ -53,125 +50,195 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
|
|
||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether this folder is at the root of the profile tree
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRootFolder => Parent == Profile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the longest timeline of all this folders children
|
||||||
|
/// </summary>
|
||||||
|
public Timeline LongestChildTimeline { get; private set; }
|
||||||
|
|
||||||
internal FolderEntity FolderEntity { get; set; }
|
internal FolderEntity FolderEntity { get; set; }
|
||||||
|
|
||||||
|
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override List<ILayerProperty> GetAllLayerProperties()
|
public override List<ILayerProperty> GetAllLayerProperties()
|
||||||
{
|
{
|
||||||
List<ILayerProperty> result = new List<ILayerProperty>();
|
List<ILayerProperty> result = new List<ILayerProperty>();
|
||||||
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
||||||
{
|
|
||||||
if (layerEffect.BaseProperties != null)
|
if (layerEffect.BaseProperties != null)
|
||||||
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override RenderElementEntity RenderElementEntity => FolderEntity;
|
|
||||||
public bool IsRootFolder => Parent == Profile;
|
|
||||||
|
|
||||||
public override void Update(double deltaTime)
|
public override void Update(double deltaTime)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Folder");
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
if (!Enabled)
|
if (!Enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Disable data bindings during an override
|
|
||||||
bool wasApplyingDataBindings = ApplyDataBindingsEnabled;
|
|
||||||
ApplyDataBindingsEnabled = false;
|
|
||||||
|
|
||||||
UpdateDisplayCondition();
|
UpdateDisplayCondition();
|
||||||
|
|
||||||
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
|
|
||||||
// to it's start
|
|
||||||
UpdateTimeline(deltaTime);
|
UpdateTimeline(deltaTime);
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
foreach (ProfileElement child in Children)
|
||||||
{
|
child.Update(deltaTime);
|
||||||
baseLayerEffect.BaseProperties?.Update(deltaTime);
|
|
||||||
baseLayerEffect.Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate the children in reverse because that's how they must be rendered too
|
|
||||||
for (int index = Children.Count - 1; index > -1; index--)
|
|
||||||
{
|
|
||||||
ProfileElement profileElement = Children[index];
|
|
||||||
profileElement.Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the old data bindings enabled state
|
|
||||||
ApplyDataBindingsEnabled = wasApplyingDataBindings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal override void UpdateTimelineLength()
|
/// <inheritdoc />
|
||||||
|
public override void Reset()
|
||||||
{
|
{
|
||||||
TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType<RenderProfileElement>().Max(c => c.TimelineLength);
|
DisplayConditionMet = false;
|
||||||
if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength)
|
Timeline.JumpToStart();
|
||||||
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
|
|
||||||
|
|
||||||
if (Parent is RenderProfileElement parent)
|
foreach (ProfileElement child in Children)
|
||||||
parent.UpdateTimelineLength();
|
child.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
|
/// <inheritdoc />
|
||||||
|
public override void AddChild(ProfileElement child, int? order = null)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Folder");
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
if (!Enabled)
|
base.AddChild(child, order);
|
||||||
|
CalculateRenderProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void RemoveChild(ProfileElement child)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
|
base.RemoveChild(child);
|
||||||
|
CalculateRenderProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalculateRenderProperties()
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
|
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
|
||||||
|
foreach (ProfileElement child in Children)
|
||||||
|
if (child is RenderProfileElement effectChild && effectChild.Path != null)
|
||||||
|
path.AddPath(effectChild.Path);
|
||||||
|
|
||||||
|
Path = path;
|
||||||
|
|
||||||
|
// Folder render properties are based on child paths and thus require an update
|
||||||
|
if (Parent is Folder folder)
|
||||||
|
folder.CalculateRenderProperties();
|
||||||
|
|
||||||
|
OnRenderPropertiesUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
|
||||||
|
foreach (ProfileElement profileElement in Children)
|
||||||
|
profileElement.Dispose();
|
||||||
|
|
||||||
|
_folderBitmap?.Dispose();
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Load()
|
||||||
|
{
|
||||||
|
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
|
||||||
|
|
||||||
|
// Load child folders
|
||||||
|
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
||||||
|
ChildrenList.Add(new Folder(Profile, this, childFolder));
|
||||||
|
// Load child layers
|
||||||
|
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
|
||||||
|
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
||||||
|
|
||||||
|
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
||||||
|
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
|
||||||
|
for (int index = 0; index < ChildrenList.Count; index++)
|
||||||
|
ChildrenList[index].Order = index + 1;
|
||||||
|
|
||||||
|
LoadRenderElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Save()
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
|
FolderEntity.Id = EntityId;
|
||||||
|
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
|
||||||
|
|
||||||
|
FolderEntity.Order = Order;
|
||||||
|
FolderEntity.Name = Name;
|
||||||
|
FolderEntity.Enabled = Enabled;
|
||||||
|
|
||||||
|
FolderEntity.ProfileId = Profile.EntityId;
|
||||||
|
FolderEntity.ExpandedPropertyGroups.Clear();
|
||||||
|
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
|
||||||
|
|
||||||
|
SaveRenderElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Rendering
|
||||||
|
|
||||||
|
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Folder");
|
||||||
|
|
||||||
|
if (!Enabled || !Children.Any(c => c.Enabled))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TimeSpan beginTime = TimelinePosition;
|
// Ensure the folder is ready
|
||||||
|
if (Path == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (stickToMainSegment)
|
// No point rendering if none of the children are going to render
|
||||||
|
if (!Children.Any(c => c is RenderProfileElement renderElement && !renderElement.Timeline.IsFinished))
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (Timeline)
|
||||||
{
|
{
|
||||||
if (!DisplayContinuously)
|
RenderFolder(Timeline, canvas, canvasInfo);
|
||||||
{
|
Timeline.ClearDelta();
|
||||||
TimeSpan position = timeOverride + StartSegmentLength;
|
|
||||||
if (position > StartSegmentLength + EndSegmentLength)
|
|
||||||
TimelinePosition = StartSegmentLength + EndSegmentLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds;
|
|
||||||
if (progress > 0)
|
|
||||||
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
|
|
||||||
else
|
|
||||||
TimelinePosition = StartSegmentLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
TimelinePosition = timeOverride;
|
|
||||||
|
|
||||||
double delta = (TimelinePosition - beginTime).TotalSeconds;
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
||||||
{
|
|
||||||
baseLayerEffect.BaseProperties?.Update(delta);
|
|
||||||
baseLayerEffect.Update(delta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
private void PrepareForRender(Timeline timeline)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
throw new ObjectDisposedException("Folder");
|
{
|
||||||
|
baseLayerEffect.BaseProperties?.Update(timeline);
|
||||||
|
baseLayerEffect.Update(timeline.Delta.TotalSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Path == null || !Enabled || !Children.Any(c => c.Enabled))
|
private void RenderFolder(Timeline timeline, SKCanvas canvas, SKImageInfo canvasInfo)
|
||||||
return;
|
{
|
||||||
|
PrepareForRender(timeline);
|
||||||
// No need to render if at the end of the timeline
|
|
||||||
if (TimelinePosition > TimelineLength)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_folderBitmap == null)
|
if (_folderBitmap == null)
|
||||||
|
{
|
||||||
_folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
|
_folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
|
||||||
|
}
|
||||||
else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height)
|
else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height)
|
||||||
{
|
{
|
||||||
_folderBitmap.Dispose();
|
_folderBitmap.Dispose();
|
||||||
@ -207,7 +274,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
folderCanvas.Save();
|
folderCanvas.Save();
|
||||||
ProfileElement profileElement = Children[index];
|
ProfileElement profileElement = Children[index];
|
||||||
profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info);
|
profileElement.Render(folderCanvas, _folderBitmap.Info);
|
||||||
folderCanvas.Restore();
|
folderCanvas.Restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,102 +292,7 @@ namespace Artemis.Core
|
|||||||
canvas.Restore();
|
canvas.Restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
#endregion
|
||||||
public override void AddChild(ProfileElement child, int? order = null)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("Folder");
|
|
||||||
|
|
||||||
base.AddChild(child, order);
|
|
||||||
UpdateTimelineLength();
|
|
||||||
CalculateRenderProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void RemoveChild(ProfileElement child)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("Folder");
|
|
||||||
|
|
||||||
base.RemoveChild(child);
|
|
||||||
UpdateTimelineLength();
|
|
||||||
CalculateRenderProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CalculateRenderProperties()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("Folder");
|
|
||||||
|
|
||||||
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
|
|
||||||
foreach (ProfileElement child in Children)
|
|
||||||
{
|
|
||||||
if (child is RenderProfileElement effectChild && effectChild.Path != null)
|
|
||||||
path.AddPath(effectChild.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Path = path;
|
|
||||||
|
|
||||||
// Folder render properties are based on child paths and thus require an update
|
|
||||||
if (Parent is Folder folder)
|
|
||||||
folder.CalculateRenderProperties();
|
|
||||||
|
|
||||||
OnRenderPropertiesUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
|
||||||
profileElement.Dispose();
|
|
||||||
|
|
||||||
_folderBitmap?.Dispose();
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Load()
|
|
||||||
{
|
|
||||||
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
|
|
||||||
|
|
||||||
// Load child folders
|
|
||||||
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
|
||||||
ChildrenList.Add(new Folder(Profile, this, childFolder));
|
|
||||||
// Load child layers
|
|
||||||
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
|
|
||||||
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
|
||||||
|
|
||||||
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
|
||||||
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
|
|
||||||
for (int index = 0; index < ChildrenList.Count; index++)
|
|
||||||
ChildrenList[index].Order = index + 1;
|
|
||||||
|
|
||||||
LoadRenderElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Save()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("Folder");
|
|
||||||
|
|
||||||
FolderEntity.Id = EntityId;
|
|
||||||
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
|
|
||||||
|
|
||||||
FolderEntity.Order = Order;
|
|
||||||
FolderEntity.Name = Name;
|
|
||||||
FolderEntity.Enabled = Enabled;
|
|
||||||
|
|
||||||
FolderEntity.ProfileId = Profile.EntityId;
|
|
||||||
FolderEntity.ExpandedPropertyGroups.Clear();
|
|
||||||
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
|
|
||||||
|
|
||||||
SaveRenderElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
@ -38,7 +37,6 @@ namespace Artemis.Core
|
|||||||
Profile = Parent.Profile;
|
Profile = Parent.Profile;
|
||||||
Name = name;
|
Name = name;
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
DisplayContinuously = true;
|
|
||||||
General = new LayerGeneralProperties();
|
General = new LayerGeneralProperties();
|
||||||
Transform = new LayerTransformProperties();
|
Transform = new LayerTransformProperties();
|
||||||
|
|
||||||
@ -47,14 +45,14 @@ namespace Artemis.Core
|
|||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
ApplyRenderElementDefaults();
|
|
||||||
|
|
||||||
Parent.AddChild(this);
|
Parent.AddChild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
||||||
{
|
{
|
||||||
LayerEntity = layerEntity;
|
LayerEntity = layerEntity;
|
||||||
|
EntityId = layerEntity.Id;
|
||||||
|
|
||||||
Profile = profile;
|
Profile = profile;
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
General = new LayerGeneralProperties();
|
General = new LayerGeneralProperties();
|
||||||
@ -68,27 +66,6 @@ namespace Artemis.Core
|
|||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal LayerEntity LayerEntity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override List<ILayerProperty> GetAllLayerProperties()
|
|
||||||
{
|
|
||||||
List<ILayerProperty> result = new List<ILayerProperty>();
|
|
||||||
result.AddRange(General.GetAllLayerProperties());
|
|
||||||
result.AddRange(Transform.GetAllLayerProperties());
|
|
||||||
if (LayerBrush?.BaseProperties != null)
|
|
||||||
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
|
|
||||||
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
|
||||||
{
|
|
||||||
if (layerEffect.BaseProperties != null)
|
|
||||||
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of all the LEDs this layer is assigned to.
|
/// A collection of all the LEDs this layer is assigned to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -131,6 +108,25 @@ namespace Artemis.Core
|
|||||||
internal set => SetAndNotify(ref _layerBrush, value);
|
internal set => SetAndNotify(ref _layerBrush, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal LayerEntity LayerEntity { get; set; }
|
||||||
|
|
||||||
|
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override List<ILayerProperty> GetAllLayerProperties()
|
||||||
|
{
|
||||||
|
List<ILayerProperty> result = new List<ILayerProperty>();
|
||||||
|
result.AddRange(General.GetAllLayerProperties());
|
||||||
|
result.AddRange(Transform.GetAllLayerProperties());
|
||||||
|
if (LayerBrush?.BaseProperties != null)
|
||||||
|
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
|
||||||
|
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
||||||
|
if (layerEffect.BaseProperties != null)
|
||||||
|
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
@ -142,7 +138,7 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
_disposed = true;
|
Disposed = true;
|
||||||
|
|
||||||
// Brush first in case it depends on any of the other disposables during it's own disposal
|
// Brush first in case it depends on any of the other disposables during it's own disposal
|
||||||
_layerBrush?.Dispose();
|
_layerBrush?.Dispose();
|
||||||
@ -195,7 +191,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal override void Save()
|
internal override void Save()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
@ -258,94 +254,30 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Update(double deltaTime)
|
public override void Update(double deltaTime)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
|
if (!Enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ensure the layer must still be displayed
|
|
||||||
UpdateDisplayCondition();
|
UpdateDisplayCondition();
|
||||||
|
|
||||||
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
|
|
||||||
// to it's start
|
|
||||||
UpdateTimeline(deltaTime);
|
UpdateTimeline(deltaTime);
|
||||||
|
|
||||||
// No point updating further than this if the layer is not going to be rendered
|
|
||||||
if (TimelinePosition > TimelineLength)
|
|
||||||
return;
|
|
||||||
|
|
||||||
General.Update(deltaTime);
|
|
||||||
Transform.Update(deltaTime);
|
|
||||||
LayerBrush.BaseProperties?.Update(deltaTime);
|
|
||||||
LayerBrush.Update(deltaTime);
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
||||||
{
|
|
||||||
baseLayerEffect.BaseProperties?.Update(deltaTime);
|
|
||||||
baseLayerEffect.Update(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal override void UpdateTimelineLength()
|
|
||||||
{
|
|
||||||
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("Layer");
|
|
||||||
|
|
||||||
if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Disable data bindings during an override
|
|
||||||
bool wasApplyingDataBindings = ApplyDataBindingsEnabled;
|
|
||||||
ApplyDataBindingsEnabled = false;
|
|
||||||
|
|
||||||
TimeSpan beginTime = TimelinePosition;
|
|
||||||
|
|
||||||
if (stickToMainSegment)
|
|
||||||
{
|
|
||||||
if (!DisplayContinuously)
|
|
||||||
TimelinePosition = StartSegmentLength + timeOverride;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds;
|
|
||||||
if (progress > 0)
|
|
||||||
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
|
|
||||||
else
|
|
||||||
TimelinePosition = StartSegmentLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
TimelinePosition = timeOverride;
|
|
||||||
|
|
||||||
double delta = (TimelinePosition - beginTime).TotalSeconds;
|
|
||||||
|
|
||||||
General.Update(delta);
|
|
||||||
Transform.Update(delta);
|
|
||||||
LayerBrush.BaseProperties?.Update(delta);
|
|
||||||
LayerBrush.Update(delta);
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
||||||
{
|
|
||||||
baseLayerEffect.BaseProperties?.Update(delta);
|
|
||||||
baseLayerEffect.Update(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the old data bindings enabled state
|
|
||||||
ApplyDataBindingsEnabled = wasApplyingDataBindings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
public override void Reset()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
DisplayConditionMet = false;
|
||||||
|
Timeline.JumpToStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
if (!Enabled || TimelinePosition > TimelineLength)
|
if (!Enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ensure the layer is ready
|
// Ensure the layer is ready
|
||||||
@ -355,8 +287,40 @@ namespace Artemis.Core
|
|||||||
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
|
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
lock (Timeline)
|
||||||
|
{
|
||||||
|
RenderLayer(Timeline, canvas);
|
||||||
|
foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
|
||||||
|
RenderLayer(extraTimeline, canvas);
|
||||||
|
Timeline.ClearDelta();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareForRender(Timeline timeline)
|
||||||
|
{
|
||||||
|
General.Update(timeline);
|
||||||
|
Transform.Update(timeline);
|
||||||
|
LayerBrush.BaseProperties?.Update(timeline);
|
||||||
|
LayerBrush.Update(timeline.Delta.TotalSeconds);
|
||||||
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
|
{
|
||||||
|
baseLayerEffect.BaseProperties?.Update(timeline);
|
||||||
|
baseLayerEffect.Update(timeline.Delta.TotalSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderLayer(Timeline timeline, SKCanvas canvas)
|
||||||
|
{
|
||||||
|
if (timeline.IsFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrepareForRender(timeline);
|
||||||
|
|
||||||
if (_layerBitmap == null)
|
if (_layerBitmap == null)
|
||||||
|
{
|
||||||
_layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
|
_layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height));
|
||||||
|
}
|
||||||
else if (_layerBitmap.Info.Width != (int) Path.Bounds.Width || _layerBitmap.Info.Height != (int) Path.Bounds.Height)
|
else if (_layerBitmap.Info.Width != (int) Path.Bounds.Width || _layerBitmap.Info.Height != (int) Path.Bounds.Height)
|
||||||
{
|
{
|
||||||
_layerBitmap.Dispose();
|
_layerBitmap.Dispose();
|
||||||
@ -365,11 +329,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
using SKPath layerPath = new SKPath(Path);
|
using SKPath layerPath = new SKPath(Path);
|
||||||
using SKCanvas layerCanvas = new SKCanvas(_layerBitmap);
|
using SKCanvas layerCanvas = new SKCanvas(_layerBitmap);
|
||||||
using SKPaint layerPaint = new SKPaint
|
using SKPaint layerPaint = new SKPaint {FilterQuality = SKFilterQuality.Low};
|
||||||
{
|
|
||||||
FilterQuality = SKFilterQuality.Low,
|
|
||||||
Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f))
|
|
||||||
};
|
|
||||||
layerCanvas.Clear();
|
layerCanvas.Clear();
|
||||||
|
|
||||||
layerPath.Transform(SKMatrix.MakeTranslation(layerPath.Bounds.Left * -1, layerPath.Bounds.Top * -1));
|
layerPath.Transform(SKMatrix.MakeTranslation(layerPath.Bounds.Left * -1, layerPath.Bounds.Top * -1));
|
||||||
@ -388,7 +348,11 @@ namespace Artemis.Core
|
|||||||
else if (General.ResizeMode.CurrentValue == LayerResizeMode.Clip)
|
else if (General.ResizeMode.CurrentValue == LayerResizeMode.Clip)
|
||||||
ClipRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath);
|
ClipRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath);
|
||||||
|
|
||||||
using SKPaint canvasPaint = new SKPaint { BlendMode = General.BlendMode.CurrentValue };
|
using SKPaint canvasPaint = new SKPaint
|
||||||
|
{
|
||||||
|
BlendMode = General.BlendMode.CurrentValue,
|
||||||
|
Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f))
|
||||||
|
};
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
baseLayerEffect.PostProcess(layerCanvas, _layerBitmap.Info, layerPath, canvasPaint);
|
baseLayerEffect.PostProcess(layerCanvas, _layerBitmap.Info, layerPath, canvasPaint);
|
||||||
|
|
||||||
@ -401,7 +365,7 @@ namespace Artemis.Core
|
|||||||
(canvasPath.Bounds.Left - targetLocation.X) * -1,
|
(canvasPath.Bounds.Left - targetLocation.X) * -1,
|
||||||
(canvasPath.Bounds.Top - targetLocation.Y) * -1)
|
(canvasPath.Bounds.Top - targetLocation.Y) * -1)
|
||||||
);
|
);
|
||||||
canvas.ClipPath(canvasPath);
|
// canvas.ClipPath(canvasPath);
|
||||||
canvas.DrawBitmap(_layerBitmap, targetLocation, canvasPaint);
|
canvas.DrawBitmap(_layerBitmap, targetLocation, canvasPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,11 +435,13 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void CalculateRenderProperties()
|
internal void CalculateRenderProperties()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
if (!Leds.Any())
|
if (!Leds.Any())
|
||||||
|
{
|
||||||
Path = new SKPath();
|
Path = new SKPath();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
|
SKPath path = new SKPath {FillType = SKPathFillType.Winding};
|
||||||
@ -498,7 +464,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
|
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
SKPoint positionProperty = Transform.Position.CurrentValue;
|
SKPoint positionProperty = Transform.Position.CurrentValue;
|
||||||
@ -522,7 +488,7 @@ namespace Artemis.Core
|
|||||||
/// <param name="path"></param>
|
/// <param name="path"></param>
|
||||||
public void IncludePathInTranslation(SKPath path, bool zeroBased)
|
public void IncludePathInTranslation(SKPath path, bool zeroBased)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
||||||
@ -548,13 +514,51 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a transformation matrix that applies the current transformation settings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zeroBased">
|
||||||
|
/// If true, treats the layer as if it is located at 0,0 instead of its actual position on the
|
||||||
|
/// surface
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The transformation matrix containing the current transformation settings</returns>
|
||||||
|
public SKMatrix GetTransformMatrix(bool zeroBased)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
||||||
|
float rotationProperty = Transform.Rotation.CurrentValue;
|
||||||
|
|
||||||
|
SKPoint anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
|
||||||
|
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
|
||||||
|
|
||||||
|
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
||||||
|
float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
|
||||||
|
float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
|
||||||
|
|
||||||
|
if (General.ResizeMode == LayerResizeMode.Normal)
|
||||||
|
{
|
||||||
|
SKMatrix transform = SKMatrix.MakeTranslation(x, y);
|
||||||
|
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
|
||||||
|
transform = transform.PostConcat(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SKMatrix transform = SKMatrix.MakeTranslation(x, y);
|
||||||
|
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y));
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
|
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
|
||||||
/// layer translations out
|
/// layer translations out
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ExcludePathFromTranslation(SKPath path, bool zeroBased)
|
public void ExcludePathFromTranslation(SKPath path, bool zeroBased)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
||||||
@ -590,7 +594,7 @@ namespace Artemis.Core
|
|||||||
/// <returns>The number of transformations applied</returns>
|
/// <returns>The number of transformations applied</returns>
|
||||||
public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased)
|
public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
||||||
@ -630,7 +634,7 @@ namespace Artemis.Core
|
|||||||
/// <param name="led">The LED to add</param>
|
/// <param name="led">The LED to add</param>
|
||||||
public void AddLed(ArtemisLed led)
|
public void AddLed(ArtemisLed led)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
_leds.Add(led);
|
_leds.Add(led);
|
||||||
@ -643,7 +647,7 @@ namespace Artemis.Core
|
|||||||
/// <param name="leds">The LEDs to add</param>
|
/// <param name="leds">The LEDs to add</param>
|
||||||
public void AddLeds(IEnumerable<ArtemisLed> leds)
|
public void AddLeds(IEnumerable<ArtemisLed> leds)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
_leds.AddRange(leds);
|
_leds.AddRange(leds);
|
||||||
@ -656,7 +660,7 @@ namespace Artemis.Core
|
|||||||
/// <param name="led">The LED to remove</param>
|
/// <param name="led">The LED to remove</param>
|
||||||
public void RemoveLed(ArtemisLed led)
|
public void RemoveLed(ArtemisLed led)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
_leds.Remove(led);
|
_leds.Remove(led);
|
||||||
@ -668,7 +672,7 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearLeds()
|
public void ClearLeds()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
_leds.Clear();
|
_leds.Clear();
|
||||||
@ -677,7 +681,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void PopulateLeds(ArtemisSurface surface)
|
internal void PopulateLeds(ArtemisSurface surface)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
List<ArtemisLed> leds = new List<ArtemisLed>();
|
List<ArtemisLed> leds = new List<ArtemisLed>();
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace Artemis.Core
|
|||||||
/// initialize these for you.
|
/// initialize these for you.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ILayerProperty : IStorageModel, IUpdateModel, IDisposable
|
public interface ILayerProperty : IStorageModel, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the description attribute applied to this property
|
/// Gets the description attribute applied to this property
|
||||||
@ -36,5 +36,11 @@ namespace Artemis.Core
|
|||||||
/// Returns a list off all data binding registrations
|
/// Returns a list off all data binding registrations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
|
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the layer properties internal state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeline">The timeline to apply to the property</param>
|
||||||
|
void Update(Timeline timeline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,20 +34,16 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Path { get; private set; }
|
public string Path { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Updates the property, applying keyframes and data bindings to the current value
|
public void Update(Timeline timeline)
|
||||||
/// </summary>
|
|
||||||
public void Update(double deltaTime)
|
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("LayerProperty");
|
throw new ObjectDisposedException("LayerProperty");
|
||||||
|
|
||||||
CurrentValue = BaseValue;
|
CurrentValue = BaseValue;
|
||||||
|
|
||||||
if (ProfileElement.ApplyKeyframesEnabled)
|
UpdateKeyframes(timeline);
|
||||||
UpdateKeyframes();
|
UpdateDataBindings(timeline);
|
||||||
if (ProfileElement.ApplyDataBindingsEnabled)
|
|
||||||
UpdateDataBindings(deltaTime);
|
|
||||||
|
|
||||||
OnUpdated();
|
OnUpdated();
|
||||||
}
|
}
|
||||||
@ -125,8 +121,7 @@ namespace Artemis.Core
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_baseValue = value;
|
_baseValue = value;
|
||||||
Update(0);
|
ReapplyUpdate();
|
||||||
OnCurrentValueSet();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +164,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
// Force an update so that the base value is applied to the current value and
|
// 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
|
// keyframes/data bindings are applied using the new base value
|
||||||
Update(0);
|
ReapplyUpdate();
|
||||||
OnCurrentValueSet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -185,6 +179,13 @@ namespace Artemis.Core
|
|||||||
CurrentValue = DefaultValue;
|
CurrentValue = DefaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReapplyUpdate()
|
||||||
|
{
|
||||||
|
ProfileElement.Timeline.ClearDelta();
|
||||||
|
Update(ProfileElement.Timeline);
|
||||||
|
OnCurrentValueSet();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Keyframes
|
#region Keyframes
|
||||||
@ -294,13 +295,13 @@ namespace Artemis.Core
|
|||||||
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateKeyframes()
|
private void UpdateKeyframes(Timeline timeline)
|
||||||
{
|
{
|
||||||
if (!KeyframesSupported || !KeyframesEnabled)
|
if (!KeyframesSupported || !KeyframesEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The current keyframe is the last keyframe before the current time
|
// 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
|
// 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;
|
int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
|
||||||
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
|
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
|
||||||
@ -314,7 +315,7 @@ namespace Artemis.Core
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
TimeSpan timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
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);
|
float keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
|
||||||
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
||||||
}
|
}
|
||||||
@ -416,11 +417,15 @@ namespace Artemis.Core
|
|||||||
OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty));
|
OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateDataBindings(double deltaTime)
|
private void UpdateDataBindings(Timeline timeline)
|
||||||
{
|
{
|
||||||
|
// To avoid data bindings applying at non-regular updating (during editing) only update when not overriden
|
||||||
|
if (timeline.IsOverridden)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (IDataBinding dataBinding in _dataBindings)
|
foreach (IDataBinding dataBinding in _dataBindings)
|
||||||
{
|
{
|
||||||
dataBinding.Update(deltaTime);
|
dataBinding.Update(timeline);
|
||||||
dataBinding.Apply();
|
dataBinding.Apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,7 +457,6 @@ namespace Artemis.Core
|
|||||||
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
|
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
|
||||||
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
|
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
|
||||||
IsLoadedFromStorage = fromStorage;
|
IsLoadedFromStorage = fromStorage;
|
||||||
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -485,12 +489,10 @@ namespace Artemis.Core
|
|||||||
_keyframes.Clear();
|
_keyframes.Clear();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
|
_keyframes.AddRange(
|
||||||
JsonConvert.DeserializeObject<T>(k.Value),
|
Entity.KeyframeEntities.Where(k => k.Position <= ProfileElement.Timeline.Length)
|
||||||
k.Position,
|
.Select(k => new LayerPropertyKeyframe<T>(JsonConvert.DeserializeObject<T>(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this))
|
||||||
(Easings.Functions) k.EasingFunction,
|
);
|
||||||
this
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
catch (JsonException e)
|
catch (JsonException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -198,11 +198,12 @@ namespace Artemis.Core
|
|||||||
layerPropertyGroup.ApplyToEntity();
|
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,
|
foreach (ILayerProperty layerProperty in LayerProperties)
|
||||||
// let properties subscribe to the update event and update themselves
|
layerProperty.Update(timeline);
|
||||||
OnPropertyGroupUpdating(new LayerPropertyGroupUpdatingEventArgs(deltaTime));
|
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||||
|
layerPropertyGroup.Update(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
|
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
|
||||||
@ -266,8 +267,6 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
internal event EventHandler<LayerPropertyGroupUpdatingEventArgs> PropertyGroupUpdating;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the property group has initialized all its children
|
/// Occurs when the property group has initialized all its children
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -284,11 +283,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler VisibilityChanged;
|
public event EventHandler VisibilityChanged;
|
||||||
|
|
||||||
internal virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e)
|
|
||||||
{
|
|
||||||
PropertyGroupUpdating?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual void OnVisibilityChanged()
|
internal virtual void OnVisibilityChanged()
|
||||||
{
|
{
|
||||||
VisibilityChanged?.Invoke(this, EventArgs.Empty);
|
VisibilityChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|||||||
@ -56,7 +56,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
if (!IsActivated)
|
if (!IsActivated)
|
||||||
throw new ArtemisCoreException($"Cannot update inactive profile: {this}");
|
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)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
if (!IsActivated)
|
if (!IsActivated)
|
||||||
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
|
throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
|
||||||
|
|
||||||
foreach (ProfileElement profileElement in Children)
|
foreach (ProfileElement profileElement in Children)
|
||||||
profileElement.Render(deltaTime, canvas, canvasInfo);
|
profileElement.Render(canvas, canvasInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
foreach (ProfileElement child in Children)
|
||||||
|
child.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
public Folder GetRootFolder()
|
public Folder GetRootFolder()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
return (Folder) Children.Single();
|
return (Folder) Children.Single();
|
||||||
@ -90,7 +97,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal override void Load()
|
internal override void Load()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
Name = ProfileEntity.Name;
|
Name = ProfileEntity.Name;
|
||||||
@ -130,12 +137,12 @@ namespace Artemis.Core
|
|||||||
ChildrenList.Clear();
|
ChildrenList.Clear();
|
||||||
|
|
||||||
IsActivated = false;
|
IsActivated = false;
|
||||||
_disposed = true;
|
Disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Save()
|
internal override void Save()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
ProfileEntity.Id = EntityId;
|
ProfileEntity.Id = EntityId;
|
||||||
@ -157,7 +164,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
if (IsActivated)
|
if (IsActivated)
|
||||||
return;
|
return;
|
||||||
@ -170,7 +177,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void PopulateLeds(ArtemisSurface surface)
|
internal void PopulateLeds(ArtemisSurface surface)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException("Profile");
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
foreach (Layer layer in GetAllLayers())
|
foreach (Layer layer in GetAllLayers())
|
||||||
|
|||||||
@ -9,13 +9,13 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
public abstract class ProfileElement : PropertyChangedBase, IDisposable
|
public abstract class ProfileElement : PropertyChangedBase, IDisposable
|
||||||
{
|
{
|
||||||
protected bool _disposed;
|
|
||||||
private bool _enabled;
|
private bool _enabled;
|
||||||
private Guid _entityId;
|
private Guid _entityId;
|
||||||
private string _name;
|
private string _name;
|
||||||
private int _order;
|
private int _order;
|
||||||
private ProfileElement _parent;
|
private ProfileElement _parent;
|
||||||
private Profile _profile;
|
private Profile _profile;
|
||||||
|
protected bool Disposed;
|
||||||
protected List<ProfileElement> ChildrenList;
|
protected List<ProfileElement> ChildrenList;
|
||||||
|
|
||||||
protected ProfileElement()
|
protected ProfileElement()
|
||||||
@ -91,7 +91,12 @@ namespace Artemis.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Renders the element
|
/// Renders the element
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
|
public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the internal state of the element
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Reset();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -108,9 +113,9 @@ namespace Artemis.Core
|
|||||||
/// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param>
|
/// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param>
|
||||||
public virtual void AddChild(ProfileElement child, int? order = null)
|
public virtual void AddChild(ProfileElement child, int? order = null)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
lock (ChildrenList)
|
lock (ChildrenList)
|
||||||
{
|
{
|
||||||
if (ChildrenList.Contains(child))
|
if (ChildrenList.Contains(child))
|
||||||
@ -152,7 +157,7 @@ namespace Artemis.Core
|
|||||||
/// <param name="child">The profile element to remove</param>
|
/// <param name="child">The profile element to remove</param>
|
||||||
public virtual void RemoveChild(ProfileElement child)
|
public virtual void RemoveChild(ProfileElement child)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
lock (ChildrenList)
|
lock (ChildrenList)
|
||||||
@ -175,7 +180,7 @@ namespace Artemis.Core
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<Folder> GetAllFolders()
|
public List<Folder> GetAllFolders()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
List<Folder> folders = new List<Folder>();
|
List<Folder> folders = new List<Folder>();
|
||||||
@ -196,7 +201,7 @@ namespace Artemis.Core
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<Layer> GetAllLayers()
|
public List<Layer> GetAllLayers()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (Disposed)
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
|
||||||
List<Layer> layers = new List<Layer>();
|
List<Layer> layers = new List<Layer>();
|
||||||
|
|||||||
@ -15,8 +15,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
protected RenderProfileElement()
|
protected RenderProfileElement()
|
||||||
{
|
{
|
||||||
ApplyDataBindingsEnabled = true;
|
Timeline = new Timeline();
|
||||||
ApplyKeyframesEnabled = true;
|
|
||||||
|
|
||||||
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
|
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
|
||||||
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
|
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
|
||||||
@ -24,49 +23,21 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
public abstract List<ILayerProperty> GetAllLayerProperties();
|
public abstract List<ILayerProperty> GetAllLayerProperties();
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
|
|
||||||
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
|
||||||
baseLayerEffect.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal void ApplyRenderElementDefaults()
|
|
||||||
{
|
|
||||||
MainSegmentLength = TimeSpan.FromSeconds(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void LoadRenderElement()
|
internal void LoadRenderElement()
|
||||||
{
|
{
|
||||||
StartSegmentLength = RenderElementEntity.StartSegmentLength;
|
|
||||||
MainSegmentLength = RenderElementEntity.MainSegmentLength;
|
|
||||||
EndSegmentLength = RenderElementEntity.EndSegmentLength;
|
|
||||||
DisplayContinuously = RenderElementEntity.DisplayContinuously;
|
|
||||||
AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline;
|
|
||||||
|
|
||||||
DisplayCondition = RenderElementEntity.DisplayCondition != null
|
DisplayCondition = RenderElementEntity.DisplayCondition != null
|
||||||
? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
|
? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
|
||||||
: new DataModelConditionGroup(null);
|
: new DataModelConditionGroup(null);
|
||||||
|
|
||||||
|
Timeline = RenderElementEntity.Timeline != null
|
||||||
|
? new Timeline(RenderElementEntity.Timeline)
|
||||||
|
: new Timeline();
|
||||||
|
|
||||||
ActivateEffects();
|
ActivateEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SaveRenderElement()
|
internal void SaveRenderElement()
|
||||||
{
|
{
|
||||||
RenderElementEntity.StartSegmentLength = StartSegmentLength;
|
|
||||||
RenderElementEntity.MainSegmentLength = MainSegmentLength;
|
|
||||||
RenderElementEntity.EndSegmentLength = EndSegmentLength;
|
|
||||||
RenderElementEntity.DisplayContinuously = DisplayContinuously;
|
|
||||||
RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline;
|
|
||||||
|
|
||||||
RenderElementEntity.LayerEffects.Clear();
|
RenderElementEntity.LayerEffects.Clear();
|
||||||
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
||||||
{
|
{
|
||||||
@ -87,8 +58,48 @@ namespace Artemis.Core
|
|||||||
// Conditions
|
// Conditions
|
||||||
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
|
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
|
||||||
DisplayCondition?.Save();
|
DisplayCondition?.Save();
|
||||||
|
|
||||||
|
// Timeline
|
||||||
|
RenderElementEntity.Timeline = Timeline?.Entity;
|
||||||
|
Timeline?.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Timeline
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the timeline associated with this render element
|
||||||
|
/// </summary>
|
||||||
|
public Timeline Timeline { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the <see cref="Timeline"/> according to the provided <paramref name="deltaTime"/> and current display condition status
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateTimeline(double deltaTime)
|
||||||
|
{
|
||||||
|
// The play mode dictates whether to stick to the main segment unless the display conditions contains events
|
||||||
|
bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet;
|
||||||
|
if (DisplayCondition != null && DisplayCondition.ContainsEvents)
|
||||||
|
stickToMainSegment = false;
|
||||||
|
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
|
||||||
|
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
|
||||||
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||||
|
baseLayerEffect.Dispose();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
private SKPath _path;
|
private SKPath _path;
|
||||||
@ -141,126 +152,6 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Timeline
|
|
||||||
|
|
||||||
private TimeSpan _startSegmentLength;
|
|
||||||
private TimeSpan _mainSegmentLength;
|
|
||||||
private TimeSpan _endSegmentLength;
|
|
||||||
private bool _displayContinuously;
|
|
||||||
private bool _alwaysFinishTimeline;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the length of the start segment
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan StartSegmentLength
|
|
||||||
{
|
|
||||||
get => _startSegmentLength;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (!SetAndNotify(ref _startSegmentLength, value)) return;
|
|
||||||
UpdateTimelineLength();
|
|
||||||
if (Parent is RenderProfileElement renderElement)
|
|
||||||
renderElement.UpdateTimelineLength();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the length of the main segment
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan MainSegmentLength
|
|
||||||
{
|
|
||||||
get => _mainSegmentLength;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (!SetAndNotify(ref _mainSegmentLength, value)) return;
|
|
||||||
UpdateTimelineLength();
|
|
||||||
if (Parent is RenderProfileElement renderElement)
|
|
||||||
renderElement.UpdateTimelineLength();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the length of the end segment
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan EndSegmentLength
|
|
||||||
{
|
|
||||||
get => _endSegmentLength;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (!SetAndNotify(ref _endSegmentLength, value)) return;
|
|
||||||
UpdateTimelineLength();
|
|
||||||
if (Parent is RenderProfileElement renderElement)
|
|
||||||
renderElement.UpdateTimelineLength();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current timeline position
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan TimelinePosition
|
|
||||||
{
|
|
||||||
get => _timelinePosition;
|
|
||||||
protected set => SetAndNotify(ref _timelinePosition, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether main timeline should repeat itself as long as display conditions are met
|
|
||||||
/// </summary>
|
|
||||||
public bool DisplayContinuously
|
|
||||||
{
|
|
||||||
get => _displayContinuously;
|
|
||||||
set => SetAndNotify(ref _displayContinuously, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether the timeline should finish when conditions are no longer met
|
|
||||||
/// </summary>
|
|
||||||
public bool AlwaysFinishTimeline
|
|
||||||
{
|
|
||||||
get => _alwaysFinishTimeline;
|
|
||||||
set => SetAndNotify(ref _alwaysFinishTimeline, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the max length of this element and any of its children
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan TimelineLength { get; protected set; }
|
|
||||||
|
|
||||||
protected double UpdateTimeline(double deltaTime)
|
|
||||||
{
|
|
||||||
TimeSpan oldPosition = _timelinePosition;
|
|
||||||
TimeSpan deltaTimeSpan = TimeSpan.FromSeconds(deltaTime);
|
|
||||||
TimeSpan mainSegmentEnd = StartSegmentLength + MainSegmentLength;
|
|
||||||
|
|
||||||
TimelinePosition += deltaTimeSpan;
|
|
||||||
// Manage segments while the condition is met
|
|
||||||
if (DisplayConditionMet)
|
|
||||||
{
|
|
||||||
// If we are at the end of the main timeline, wrap around back to the beginning
|
|
||||||
if (DisplayContinuously && TimelinePosition >= mainSegmentEnd)
|
|
||||||
TimelinePosition = StartSegmentLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Skip to the last segment if conditions are no longer met
|
|
||||||
if (!AlwaysFinishTimeline && TimelinePosition < mainSegmentEnd)
|
|
||||||
TimelinePosition = mainSegmentEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (TimelinePosition - oldPosition).TotalSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal abstract void UpdateTimelineLength();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the progress of the element
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="timeOverride"></param>
|
|
||||||
/// <param name="stickToMainSegment"></param>
|
|
||||||
public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Effect management
|
#region Effect management
|
||||||
|
|
||||||
protected List<BaseLayerEffect> _layerEffects;
|
protected List<BaseLayerEffect> _layerEffects;
|
||||||
@ -392,11 +283,10 @@ namespace Artemis.Core
|
|||||||
public bool DisplayConditionMet
|
public bool DisplayConditionMet
|
||||||
{
|
{
|
||||||
get => _displayConditionMet;
|
get => _displayConditionMet;
|
||||||
private set => SetAndNotify(ref _displayConditionMet, value);
|
protected set => SetAndNotify(ref _displayConditionMet, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataModelConditionGroup _displayCondition;
|
private DataModelConditionGroup _displayCondition;
|
||||||
private TimeSpan _timelinePosition;
|
|
||||||
private bool _displayConditionMet;
|
private bool _displayConditionMet;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -409,20 +299,46 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether keyframes should be applied when this profile element updates
|
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ApplyKeyframesEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether data bindings should be applied when this profile element updates
|
|
||||||
/// </summary>
|
|
||||||
public bool ApplyDataBindingsEnabled { get; set; }
|
|
||||||
|
|
||||||
public void UpdateDisplayCondition()
|
public void UpdateDisplayCondition()
|
||||||
{
|
{
|
||||||
bool conditionMet = DisplayCondition == null || DisplayCondition.Evaluate();
|
if (DisplayCondition == null)
|
||||||
if (conditionMet && !DisplayConditionMet)
|
{
|
||||||
TimelinePosition = TimeSpan.Zero;
|
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;
|
DisplayConditionMet = conditionMet;
|
||||||
}
|
}
|
||||||
|
|||||||
493
src/Artemis.Core/Models/Profile/Timeline.cs
Normal file
493
src/Artemis.Core/Models/Profile/Timeline.cs
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a timeline used by profile elements
|
||||||
|
/// </summary>
|
||||||
|
public class Timeline : PropertyChangedBase, IStorageModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="Timeline" /> class
|
||||||
|
/// </summary>
|
||||||
|
public Timeline()
|
||||||
|
{
|
||||||
|
Entity = new TimelineEntity();
|
||||||
|
_extraTimelines = new List<Timeline>();
|
||||||
|
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Timeline(TimelineEntity entity)
|
||||||
|
{
|
||||||
|
Entity = entity;
|
||||||
|
_extraTimelines = new List<Timeline>();
|
||||||
|
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timeline(Timeline parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Extra timelines
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an extra timeline to this timeline
|
||||||
|
/// </summary>
|
||||||
|
public void AddExtraTimeline()
|
||||||
|
{
|
||||||
|
_extraTimelines.Add(new Timeline(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all extra timelines from this timeline
|
||||||
|
/// </summary>
|
||||||
|
public void ClearExtraTimelines()
|
||||||
|
{
|
||||||
|
_extraTimelines.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
private TimeSpan _position;
|
||||||
|
private TimeSpan _lastDelta;
|
||||||
|
private TimeLineEventOverlapMode _eventOverlapMode;
|
||||||
|
private TimelinePlayMode _playMode;
|
||||||
|
private TimelineStopMode _stopMode;
|
||||||
|
private readonly List<Timeline> _extraTimelines;
|
||||||
|
private TimeSpan _startSegmentLength;
|
||||||
|
private TimeSpan _mainSegmentLength;
|
||||||
|
private TimeSpan _endSegmentLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent this timeline is an extra timeline of
|
||||||
|
/// </summary>
|
||||||
|
public Timeline? Parent { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current position of the timeline
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
private set => SetAndNotify(ref _position, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the cumulative delta of all calls to <see cref="Update" /> that took place after the last call to <see cref="ClearDelta"/>
|
||||||
|
/// <para>
|
||||||
|
/// Note: If this is an extra timeline <see cref="Delta" /> is always equal to <see cref="DeltaToParent" />
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Delta
|
||||||
|
{
|
||||||
|
get => Parent == null ? _lastDelta : DeltaToParent;
|
||||||
|
private set => SetAndNotify(ref _lastDelta, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delta to this timeline's <see cref="Parent" />
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan DeltaToParent => Parent != null ? Position - Parent.Position : TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||||
|
/// </summary>
|
||||||
|
public TimelinePlayMode PlayMode
|
||||||
|
{
|
||||||
|
get => _playMode;
|
||||||
|
set => SetAndNotify(ref _playMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
|
||||||
|
/// </summary>
|
||||||
|
public TimelineStopMode StopMode
|
||||||
|
{
|
||||||
|
get => _stopMode;
|
||||||
|
set => SetAndNotify(ref _stopMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mode in which the render element responds to display condition events being fired before the
|
||||||
|
/// timeline finished
|
||||||
|
/// </summary>
|
||||||
|
public TimeLineEventOverlapMode EventOverlapMode
|
||||||
|
{
|
||||||
|
get => _eventOverlapMode;
|
||||||
|
set => SetAndNotify(ref _eventOverlapMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of extra copies of the timeline applied to this timeline
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<Timeline> ExtraTimelines => _extraTimelines.AsReadOnly();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the timeline has finished its run
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFinished => Position > Length || Length == TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the timeline progress has been overridden
|
||||||
|
/// </summary>
|
||||||
|
public bool IsOverridden { get; private set; }
|
||||||
|
|
||||||
|
#region Segments
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total length of this timeline
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Length => StartSegmentLength + MainSegmentLength + EndSegmentLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the length of the start segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan StartSegmentLength
|
||||||
|
{
|
||||||
|
get => _startSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetAndNotify(ref _startSegmentLength, value))
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.Start, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the length of the main segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan MainSegmentLength
|
||||||
|
{
|
||||||
|
get => _mainSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetAndNotify(ref _mainSegmentLength, value))
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.Main, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the length of the end segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan EndSegmentLength
|
||||||
|
{
|
||||||
|
get => _endSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetAndNotify(ref _endSegmentLength, value))
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.End, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the start position of the main segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan MainSegmentStartPosition
|
||||||
|
{
|
||||||
|
get => StartSegmentEndPosition;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
StartSegmentEndPosition = value;
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.Main, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the end position of the end segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan EndSegmentStartPosition
|
||||||
|
{
|
||||||
|
get => MainSegmentEndPosition;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
MainSegmentEndPosition = value;
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.End, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the end position of the start segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan StartSegmentEndPosition
|
||||||
|
{
|
||||||
|
get => StartSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
StartSegmentLength = value;
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.Start, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the end position of the main segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan MainSegmentEndPosition
|
||||||
|
{
|
||||||
|
get => StartSegmentEndPosition + MainSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero;
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.Main, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the end position of the end segment
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan EndSegmentEndPosition
|
||||||
|
{
|
||||||
|
get => MainSegmentEndPosition + EndSegmentLength;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
EndSegmentLength = value - MainSegmentEndPosition >= TimeSpan.Zero ? value - MainSegmentEndPosition : TimeSpan.Zero;
|
||||||
|
NotifySegmentShiftAt(TimelineSegment.End, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TimelineEntity Entity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies the right segments in a way that I don't have to think about it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segment">The segment that was updated</param>
|
||||||
|
/// <param name="startUpdated">Whether the start point of the <paramref name="segment" /> was updated</param>
|
||||||
|
private void NotifySegmentShiftAt(TimelineSegment segment, bool startUpdated)
|
||||||
|
{
|
||||||
|
if (segment <= TimelineSegment.End)
|
||||||
|
{
|
||||||
|
if (startUpdated || segment < TimelineSegment.End)
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentStartPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment <= TimelineSegment.Main)
|
||||||
|
{
|
||||||
|
if (startUpdated || segment < TimelineSegment.Main)
|
||||||
|
NotifyOfPropertyChange(nameof(MainSegmentStartPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment <= TimelineSegment.Start) NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
|
||||||
|
|
||||||
|
NotifyOfPropertyChange(nameof(Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Updating
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the timeline, applying the provided <paramref name="delta" /> to the <see cref="Position" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delta">The amount of time to apply to the position</param>
|
||||||
|
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||||
|
public void Update(TimeSpan delta, bool stickToMainSegment)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
Delta += delta;
|
||||||
|
Position += delta;
|
||||||
|
IsOverridden = false;
|
||||||
|
|
||||||
|
if (stickToMainSegment && Position >= MainSegmentStartPosition)
|
||||||
|
{
|
||||||
|
// If the main segment has no length, simply stick to the start of the segment
|
||||||
|
if (MainSegmentLength == TimeSpan.Zero)
|
||||||
|
Position = MainSegmentStartPosition;
|
||||||
|
// Ensure wrapping back around retains the delta time
|
||||||
|
else
|
||||||
|
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Timeline extraTimeline in _extraTimelines)
|
||||||
|
extraTimeline.Update(delta, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves the position of the timeline backwards to the very start of the timeline
|
||||||
|
/// </summary>
|
||||||
|
public void JumpToStart()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (Position == TimeSpan.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Delta = TimeSpan.Zero - Position;
|
||||||
|
Position = TimeSpan.Zero;
|
||||||
|
|
||||||
|
_extraTimelines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves the position of the timeline forwards to the beginning of the end segment
|
||||||
|
/// </summary>
|
||||||
|
public void JumpToEndSegment()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (Position >= EndSegmentStartPosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Delta = EndSegmentStartPosition - Position;
|
||||||
|
Position = EndSegmentStartPosition;
|
||||||
|
|
||||||
|
_extraTimelines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves the position of the timeline forwards to the very end of the timeline
|
||||||
|
/// </summary>
|
||||||
|
public void JumpToEnd()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (Position >= EndSegmentEndPosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Delta = EndSegmentEndPosition - Position;
|
||||||
|
Position = EndSegmentEndPosition;
|
||||||
|
|
||||||
|
_extraTimelines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the <see cref="Position" /> to the specified time and clears any extra time lines
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to set the timeline to</param>
|
||||||
|
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||||
|
public void Override(TimeSpan position, bool stickToMainSegment)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
Delta += position - Position;
|
||||||
|
Position = position;
|
||||||
|
IsOverridden = true;
|
||||||
|
|
||||||
|
if (stickToMainSegment && Position >= MainSegmentStartPosition)
|
||||||
|
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
||||||
|
|
||||||
|
_extraTimelines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="Delta" /> to <see cref="TimeSpan.Zero" />
|
||||||
|
/// </summary>
|
||||||
|
public void ClearDelta()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
Delta = TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Storage
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
StartSegmentLength = Entity.StartSegmentLength;
|
||||||
|
MainSegmentLength = Entity.MainSegmentLength;
|
||||||
|
EndSegmentLength = Entity.EndSegmentLength;
|
||||||
|
PlayMode = (TimelinePlayMode) Entity.PlayMode;
|
||||||
|
StopMode = (TimelineStopMode) Entity.StopMode;
|
||||||
|
EventOverlapMode = (TimeLineEventOverlapMode) Entity.EventOverlapMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
Entity.StartSegmentLength = StartSegmentLength;
|
||||||
|
Entity.MainSegmentLength = MainSegmentLength;
|
||||||
|
Entity.EndSegmentLength = EndSegmentLength;
|
||||||
|
Entity.PlayMode = (int) PlayMode;
|
||||||
|
Entity.StopMode = (int) StopMode;
|
||||||
|
Entity.EventOverlapMode = (int) EventOverlapMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Progress: {Position}/{Length} - delta: {Delta}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum TimelineSegment
|
||||||
|
{
|
||||||
|
Start,
|
||||||
|
Main,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a mode for render elements to start their timeline when display conditions are met
|
||||||
|
/// </summary>
|
||||||
|
public enum TimelinePlayMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Continue repeating the main segment of the timeline while the condition is met
|
||||||
|
/// </summary>
|
||||||
|
Repeat,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only play the timeline once when the condition is met
|
||||||
|
/// </summary>
|
||||||
|
Once
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
|
||||||
|
/// </summary>
|
||||||
|
public enum TimelineStopMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When conditions are no longer met, finish the the current run of the main timeline
|
||||||
|
/// </summary>
|
||||||
|
Finish,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When conditions are no longer met, skip to the end segment of the timeline
|
||||||
|
/// </summary>
|
||||||
|
SkipToEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a mode for render elements to start their timeline when display conditions events are fired
|
||||||
|
/// </summary>
|
||||||
|
public enum TimeLineEventOverlapMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stop the current run and restart the timeline
|
||||||
|
/// </summary>
|
||||||
|
Restart,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ignore subsequent event fires until the timeline finishes
|
||||||
|
/// </summary>
|
||||||
|
Ignore,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Play another copy of the timeline on top of the current run
|
||||||
|
/// </summary>
|
||||||
|
Copy
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -81,8 +81,9 @@ namespace Artemis.Core.Modules
|
|||||||
|
|
||||||
internal override void InternalDisablePlugin()
|
internal override void InternalDisablePlugin()
|
||||||
{
|
{
|
||||||
DataModel = null;
|
Deactivate(true);
|
||||||
base.InternalDisablePlugin();
|
base.InternalDisablePlugin();
|
||||||
|
DataModel = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ namespace Artemis.Core.Modules
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the currently active profile
|
/// Gets the currently active profile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Profile ActiveProfile { get; private set; }
|
public Profile? ActiveProfile { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disables updating the profile, rendering does continue
|
/// Disables updating the profile, rendering does continue
|
||||||
@ -174,7 +175,7 @@ namespace Artemis.Core.Modules
|
|||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
// Render the profile
|
// Render the profile
|
||||||
ActiveProfile?.Render(deltaTime, canvas, canvasInfo);
|
ActiveProfile?.Render(canvas, canvasInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileRendered(deltaTime, surface, canvas, canvasInfo);
|
ProfileRendered(deltaTime, surface, canvas, canvasInfo);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ namespace Artemis.Core
|
|||||||
/// The action to call every time the interval has passed. The delta time parameter represents the
|
/// The action to call every time the interval has passed. The delta time parameter represents the
|
||||||
/// time passed since the last update in seconds
|
/// time passed since the last update in seconds
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>The resulting plugin update registration</returns>
|
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
|
||||||
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
|
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
|
||||||
{
|
{
|
||||||
if (action == null)
|
if (action == null)
|
||||||
|
|||||||
@ -1,47 +1,55 @@
|
|||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.ProfileEntity, Artemis.Storage",
|
"$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",
|
"PluginGuid": "0de2991a-d7b8-4f61-ae4e-6623849215b5",
|
||||||
"Name": "Intro animation",
|
"Name": "Intro animation - Imported",
|
||||||
"IsActive": true,
|
"IsActive": true,
|
||||||
"Folders": {
|
"Folders": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.FolderEntity, Artemis.Storage",
|
||||||
"Id": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
"Id": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
||||||
"ParentId": "eb4f487b-475b-408f-a84f-733412d41b44",
|
"ParentId": "824a235d-da46-4c82-a16b-13efe347f492",
|
||||||
"Order": 1,
|
"Order": 1,
|
||||||
"Name": "Root folder",
|
"Name": "Root folder",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
|
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
||||||
"StartSegmentLength": "00:00:00",
|
|
||||||
"MainSegmentLength": "00:00:05",
|
|
||||||
"EndSegmentLength": "00:00:00",
|
|
||||||
"DisplayContinuously": true,
|
|
||||||
"AlwaysFinishTimeline": false,
|
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"PropertyEntities": {
|
"PropertyEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"ExpandedPropertyGroups": {
|
"ExpandedPropertyGroups": {
|
||||||
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$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": {
|
"Layers": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage",
|
||||||
@ -51,25 +59,17 @@
|
|||||||
"Name": "Noise",
|
"Name": "Noise",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Leds": {
|
"Leds": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
|
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
||||||
"StartSegmentLength": "00:00:00",
|
|
||||||
"MainSegmentLength": "00:00:05",
|
|
||||||
"EndSegmentLength": "00:00:00",
|
|
||||||
"DisplayContinuously": false,
|
|
||||||
"AlwaysFinishTimeline": false,
|
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"PropertyEntities": {
|
"PropertyEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
@ -78,8 +78,11 @@
|
|||||||
"Value": "1",
|
"Value": "1",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -90,8 +93,11 @@
|
|||||||
"Value": "1",
|
"Value": "1",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -102,8 +108,11 @@
|
|||||||
"Value": "3",
|
"Value": "3",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -114,8 +123,11 @@
|
|||||||
"Value": "{\"BrushPluginGuid\":\"61cbbf01-8d69-4ede-a972-f3f269da66d9\",\"BrushType\":\"NoiseBrush\"}",
|
"Value": "{\"BrushPluginGuid\":\"61cbbf01-8d69-4ede-a972-f3f269da66d9\",\"BrushType\":\"NoiseBrush\"}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -126,8 +138,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -138,8 +153,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -150,8 +168,7 @@
|
|||||||
"Value": "{\"IsEmpty\":false,\"Width\":500.0,\"Height\":500.0}",
|
"Value": "{\"IsEmpty\":false,\"Width\":500.0,\"Height\":500.0}",
|
||||||
"KeyframesEnabled": true,
|
"KeyframesEnabled": true,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
|
||||||
@ -168,6 +185,10 @@
|
|||||||
"EasingFunction": 0
|
"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",
|
"Value": "-45.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -189,9 +213,15 @@
|
|||||||
"Value": "100.0",
|
"Value": "100.0",
|
||||||
"KeyframesEnabled": true,
|
"KeyframesEnabled": true,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$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",
|
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
|
||||||
"Position": "00:00:04",
|
"Position": "00:00:04",
|
||||||
@ -207,6 +237,10 @@
|
|||||||
"EasingFunction": 0
|
"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",
|
"Value": "1",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -228,8 +265,11 @@
|
|||||||
"Value": "\"#ff009688\"",
|
"Value": "\"#ff009688\"",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -240,8 +280,11 @@
|
|||||||
"Value": "\"#ff00ffe7\"",
|
"Value": "\"#ff00ffe7\"",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -249,12 +292,14 @@
|
|||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
"PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9",
|
"PluginGuid": "61cbbf01-8d69-4ede-a972-f3f269da66d9",
|
||||||
"Path": "LayerBrush.GradientColor",
|
"Path": "LayerBrush.GradientColor",
|
||||||
"Value":
|
"Value": "{\"Stops\":[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}]}",
|
||||||
"{\"Stops\":[{\"Color\":\"#ff0b4a40\",\"Position\":0.0},{\"Color\":\"#ff00897c\",\"Position\":0.242},{\"Color\":\"#ffffffff\",\"Position\":1.0},{\"Color\":\"#ff00ffe6\",\"Position\":0.67391306}],\"Rotation\":0.0}",
|
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -265,8 +310,11 @@
|
|||||||
"Value": "{\"IsEmpty\":false,\"Width\":44.9,\"Height\":31.0}",
|
"Value": "{\"IsEmpty\":false,\"Width\":44.9,\"Height\":31.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -277,8 +325,11 @@
|
|||||||
"Value": "228.5",
|
"Value": "228.5",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -289,8 +340,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -301,8 +355,11 @@
|
|||||||
"Value": "25.0",
|
"Value": "25.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,8 +368,25 @@
|
|||||||
"ExpandedPropertyGroups": {
|
"ExpandedPropertyGroups": {
|
||||||
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
||||||
"$values": [
|
"$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",
|
"Name": "Exploison",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Leds": {
|
"Leds": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
|
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
||||||
"StartSegmentLength": "00:00:00",
|
|
||||||
"MainSegmentLength": "00:00:03",
|
|
||||||
"EndSegmentLength": "00:00:00",
|
|
||||||
"DisplayContinuously": false,
|
|
||||||
"AlwaysFinishTimeline": false,
|
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"PropertyEntities": {
|
"PropertyEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
@ -350,8 +416,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -362,8 +431,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -374,8 +446,11 @@
|
|||||||
"Value": "3",
|
"Value": "3",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -386,8 +461,11 @@
|
|||||||
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
|
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -398,8 +476,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -410,8 +491,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -422,8 +506,7 @@
|
|||||||
"Value": "{\"IsEmpty\":false,\"Width\":110.03,\"Height\":340.37}",
|
"Value": "{\"IsEmpty\":false,\"Width\":110.03,\"Height\":340.37}",
|
||||||
"KeyframesEnabled": true,
|
"KeyframesEnabled": true,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage",
|
||||||
@ -440,6 +523,10 @@
|
|||||||
"EasingFunction": 0
|
"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",
|
"Value": "0.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -461,8 +551,11 @@
|
|||||||
"Value": "100.0",
|
"Value": "100.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -473,8 +566,11 @@
|
|||||||
"Value": "2",
|
"Value": "2",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -485,8 +581,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -497,8 +596,11 @@
|
|||||||
"Value": "\"#ffff0000\"",
|
"Value": "\"#ffff0000\"",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -506,12 +608,14 @@
|
|||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
||||||
"Path": "LayerBrush.Colors",
|
"Path": "LayerBrush.Colors",
|
||||||
"Value":
|
"Value": "{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.492},{\"Color\":\"#ffedff00\",\"Position\":0.905},{\"Color\":\"#00ff0000\",\"Position\":1.0}]}",
|
||||||
"{\"Stops\":[{\"Color\":\"#00ff0000\",\"Position\":0.0},{\"Color\":\"#ffff8800\",\"Position\":0.492},{\"Color\":\"#ffedff00\",\"Position\":0.905},{\"Color\":\"#00ff0000\",\"Position\":1.0}],\"Rotation\":0.0}",
|
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -522,8 +626,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -534,8 +641,11 @@
|
|||||||
"Value": "0.0",
|
"Value": "0.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -546,8 +656,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -558,8 +671,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -568,35 +684,44 @@
|
|||||||
"ExpandedPropertyGroups": {
|
"ExpandedPropertyGroups": {
|
||||||
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
"$type": "System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib",
|
||||||
"$values": []
|
"$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",
|
"$type": "Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage",
|
||||||
"Id": "f046f56f-a236-4ed6-bbd9-b5a4731878cf",
|
"Id": "f046f56f-a236-4ed6-bbd9-b5a4731878cf",
|
||||||
"ParentId": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
"ParentId": "cc21b67c-3485-4dc6-b2af-105fda42a915",
|
||||||
"Order": 2,
|
"Order": 3,
|
||||||
"Name": "Background",
|
"Name": "Background",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Leds": {
|
"Leds": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LedEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"Profile": null,
|
"Profile": null,
|
||||||
"ProfileId": "eb4f487b-475b-408f-a84f-733412d41b44",
|
"ProfileId": "824a235d-da46-4c82-a16b-13efe347f492",
|
||||||
"StartSegmentLength": "00:00:00",
|
|
||||||
"MainSegmentLength": "00:00:03",
|
|
||||||
"EndSegmentLength": "00:00:00",
|
|
||||||
"DisplayContinuously": false,
|
|
||||||
"AlwaysFinishTimeline": false,
|
|
||||||
"LayerEffects": {
|
"LayerEffects": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.LayerEffectEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": []
|
"$values": []
|
||||||
},
|
},
|
||||||
"PropertyEntities": {
|
"PropertyEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage]], System.Private.CoreLib",
|
|
||||||
"$values": [
|
"$values": [
|
||||||
{
|
{
|
||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
@ -605,8 +730,11 @@
|
|||||||
"Value": "1",
|
"Value": "1",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -617,8 +745,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -629,8 +760,11 @@
|
|||||||
"Value": "3",
|
"Value": "3",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -641,8 +775,11 @@
|
|||||||
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
|
"Value": "{\"BrushPluginGuid\":\"92a9d6ba-6f7a-4937-94d5-c1d715b4141a\",\"BrushType\":\"ColorBrush\"}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -653,8 +790,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -665,8 +805,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -677,8 +820,11 @@
|
|||||||
"Value": "{\"IsEmpty\":false,\"Width\":100.0,\"Height\":100.0}",
|
"Value": "{\"IsEmpty\":false,\"Width\":100.0,\"Height\":100.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -689,8 +835,11 @@
|
|||||||
"Value": "0.0",
|
"Value": "0.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -701,8 +850,11 @@
|
|||||||
"Value": "100.0",
|
"Value": "100.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -713,8 +865,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -725,8 +880,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -737,8 +895,11 @@
|
|||||||
"Value": "\"#ff000000\"",
|
"Value": "\"#ff000000\"",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -746,12 +907,14 @@
|
|||||||
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
"$type": "Artemis.Storage.Entities.Profile.PropertyEntity, Artemis.Storage",
|
||||||
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
"PluginGuid": "92a9d6ba-6f7a-4937-94d5-c1d715b4141a",
|
||||||
"Path": "LayerBrush.Colors",
|
"Path": "LayerBrush.Colors",
|
||||||
"Value":
|
"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}]}",
|
||||||
"{\"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}",
|
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -762,8 +925,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -774,8 +940,11 @@
|
|||||||
"Value": "0.0",
|
"Value": "0.0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -786,8 +955,11 @@
|
|||||||
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
"Value": "{\"IsEmpty\":true,\"Length\":0.0,\"LengthSquared\":0.0,\"X\":0.0,\"Y\":0.0}",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -798,8 +970,11 @@
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"KeyframesEnabled": false,
|
"KeyframesEnabled": false,
|
||||||
"KeyframeEntities": {
|
"KeyframeEntities": {
|
||||||
"$type":
|
"$type": "System.Collections.Generic.List`1[[Artemis.Storage.Entities.Profile.KeyframeEntity, Artemis.Storage]], System.Private.CoreLib",
|
||||||
"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": []
|
"$values": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,6 +985,23 @@
|
|||||||
"$values": [
|
"$values": [
|
||||||
"LayerBrush"
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -123,7 +123,7 @@ namespace Artemis.Core.Services
|
|||||||
_introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info);
|
_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)
|
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@ -146,7 +146,7 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
|
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
Converters = new List<JsonConverter> {new SKColorConverter()}
|
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -221,8 +221,7 @@ namespace Artemis.Core.Services
|
|||||||
Type pluginType = pluginTypes.Single();
|
Type pluginType = pluginTypes.Single();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IParameter[] parameters = new IParameter[]
|
IParameter[] parameters = {
|
||||||
{
|
|
||||||
new Parameter("PluginInfo", pluginInfo, false)
|
new Parameter("PluginInfo", pluginInfo, false)
|
||||||
};
|
};
|
||||||
pluginInfo.Kernel = new ChildKernel(_kernel);
|
pluginInfo.Kernel = new ChildKernel(_kernel);
|
||||||
|
|||||||
@ -72,6 +72,8 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
RegisterModifierType(Constants.CorePluginInfo, new SKColorSumModifierType());
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorSumModifierType());
|
||||||
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorSaturateModifierType());
|
||||||
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorDesaturateModifierType());
|
||||||
RegisterModifierType(Constants.CorePluginInfo, new SKColorBrightenModifierType());
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorBrightenModifierType());
|
||||||
RegisterModifierType(Constants.CorePluginInfo, new SKColorDarkenModifierType());
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorDarkenModifierType());
|
||||||
RegisterModifierType(Constants.CorePluginInfo, new SKColorRotateHueModifierType());
|
RegisterModifierType(Constants.CorePluginInfo, new SKColorRotateHueModifierType());
|
||||||
|
|||||||
@ -49,6 +49,11 @@ namespace Artemis.Core.Services
|
|||||||
/// <param name="profileModule"></param>
|
/// <param name="profileModule"></param>
|
||||||
void ActivateLastProfile(ProfileModule profileModule);
|
void ActivateLastProfile(ProfileModule profileModule);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reloads the currently active profile on the provided profile module
|
||||||
|
/// </summary>
|
||||||
|
void ReloadProfile(ProfileModule module);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchronously activates the last profile of the given profile module using a fade animation
|
/// Asynchronously activates the last profile of the given profile module using a fade animation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -82,6 +82,19 @@ namespace Artemis.Core.Services
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReloadProfile(ProfileModule module)
|
||||||
|
{
|
||||||
|
if (module.ActiveProfile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId);
|
||||||
|
Profile profile = new Profile(module, entity);
|
||||||
|
InstantiateProfile(profile);
|
||||||
|
|
||||||
|
module.ChangeActiveProfile(null, _surfaceService.ActiveSurface);
|
||||||
|
module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
|
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
|
||||||
{
|
{
|
||||||
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
||||||
|
|||||||
@ -24,17 +24,6 @@ namespace Artemis.Core
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogListPredicateDeserializationFailure(DataModelConditionListPredicate dataModelConditionPredicate, JsonException exception)
|
|
||||||
{
|
|
||||||
_logger.Warning(
|
|
||||||
exception,
|
|
||||||
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
|
|
||||||
dataModelConditionPredicate.Entity.LeftPath?.Path,
|
|
||||||
dataModelConditionPredicate.Entity.OperatorType,
|
|
||||||
dataModelConditionPredicate.Entity.RightPath?.Path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception)
|
public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName);
|
_logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ namespace Artemis.Core
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
AnimationProfile.Update(deltaTime);
|
AnimationProfile.Update(deltaTime);
|
||||||
AnimationProfile.Render(deltaTime, canvas, bitmapInfo);
|
AnimationProfile.Render(canvas, bitmapInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateIntroProfile()
|
private void CreateIntroProfile()
|
||||||
|
|||||||
@ -6,16 +6,11 @@ namespace Artemis.Storage.Entities.Profile.Abstract
|
|||||||
{
|
{
|
||||||
public abstract class RenderElementEntity
|
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<LayerEffectEntity> LayerEffects { get; set; }
|
public List<LayerEffectEntity> LayerEffects { get; set; }
|
||||||
public List<PropertyEntity> PropertyEntities { get; set; }
|
public List<PropertyEntity> PropertyEntities { get; set; }
|
||||||
public List<string> ExpandedPropertyGroups { get; set; }
|
public List<string> ExpandedPropertyGroups { get; set; }
|
||||||
|
|
||||||
public DataModelConditionGroupEntity DisplayCondition { get; set; }
|
public DataModelConditionGroupEntity DisplayCondition { get; set; }
|
||||||
|
public TimelineEntity Timeline { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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<DataModelConditionPartEntity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataModelPathEntity EventPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||||
|
{
|
||||||
|
public class DataModelConditionEventPredicateEntity : DataModelConditionPredicateEntity
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||||
|
{
|
||||||
|
public class DataModelConditionGeneralPredicateEntity : DataModelConditionPredicateEntity
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Profile.Conditions
|
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||||
|
|||||||
@ -1,20 +1,6 @@
|
|||||||
using System;
|
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,7 +3,7 @@ using Artemis.Storage.Entities.Profile.Abstract;
|
|||||||
|
|
||||||
namespace Artemis.Storage.Entities.Profile.Conditions
|
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||||
{
|
{
|
||||||
public class DataModelConditionPredicateEntity : DataModelConditionPartEntity
|
public abstract class DataModelConditionPredicateEntity : DataModelConditionPartEntity
|
||||||
{
|
{
|
||||||
public int PredicateType { get; set; }
|
public int PredicateType { get; set; }
|
||||||
public DataModelPathEntity LeftPath { 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
|
// Stored as a string to be able to control serialization and deserialization ourselves
|
||||||
public string RightStaticValue { get; set; }
|
public string RightStaticValue { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,5 +6,14 @@ namespace Artemis.Storage.Entities.Profile
|
|||||||
{
|
{
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public Guid? DataModelGuid { get; set; }
|
public Guid? DataModelGuid { get; set; }
|
||||||
|
|
||||||
|
public PathWrapperType WrapperType { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PathWrapperType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
List,
|
||||||
|
Event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
src/Artemis.Storage/Entities/Profile/TimelineEntity.cs
Normal file
15
src/Artemis.Storage/Entities/Profile/TimelineEntity.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,31 +13,32 @@ namespace Artemis.Storage.Migrations
|
|||||||
|
|
||||||
public void Apply(LiteRepository repository)
|
public void Apply(LiteRepository repository)
|
||||||
{
|
{
|
||||||
List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
|
// Lesson for next time: Use BsonDocuments in migrations
|
||||||
foreach (ProfileEntity profileEntity in profiles)
|
// List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
|
||||||
{
|
// foreach (ProfileEntity profileEntity in profiles)
|
||||||
foreach (FolderEntity folder in profileEntity.Folders.Where(f => f.MainSegmentLength == TimeSpan.Zero))
|
// {
|
||||||
{
|
// 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.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
|
||||||
if (folder.MainSegmentLength == TimeSpan.Zero)
|
// folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
|
||||||
folder.MainSegmentLength = TimeSpan.FromSeconds(5);
|
// if (folder.MainSegmentLength == TimeSpan.Zero)
|
||||||
|
// folder.MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||||
folder.DisplayContinuously = true;
|
//
|
||||||
}
|
// folder.PlayMode = 0;
|
||||||
|
// }
|
||||||
foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero))
|
//
|
||||||
{
|
// 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.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
|
||||||
if (layer.MainSegmentLength == TimeSpan.Zero)
|
// layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
|
||||||
layer.MainSegmentLength = TimeSpan.FromSeconds(5);
|
// if (layer.MainSegmentLength == TimeSpan.Zero)
|
||||||
|
// layer.MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||||
layer.DisplayContinuously = true;
|
//
|
||||||
}
|
// layer.PlayMode = 0;
|
||||||
|
// }
|
||||||
repository.Update(profileEntity);
|
//
|
||||||
}
|
// repository.Update(profileEntity);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
49
src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs
Normal file
49
src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs
Normal file
@ -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<BsonDocument> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,15 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=behaviors/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=converters/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=datamodelvisualization/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=datamodelvisualization_005Cshared/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=dependencyproperties/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=propertyinput/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdatamodelvisualization/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdialog/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@ -97,7 +97,11 @@ namespace Artemis.UI.Shared.Input
|
|||||||
public bool IsDataModelViewModelOpen
|
public bool IsDataModelViewModelOpen
|
||||||
{
|
{
|
||||||
get => _isDataModelViewModelOpen;
|
get => _isDataModelViewModelOpen;
|
||||||
set => SetAndNotify(ref _isDataModelViewModelOpen, value);
|
set
|
||||||
|
{
|
||||||
|
if (!SetAndNotify(ref _isDataModelViewModelOpen, value)) return;
|
||||||
|
if (value) UpdateDataModelVisualization();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelPath DataModelPath
|
public DataModelPath DataModelPath
|
||||||
@ -127,6 +131,8 @@ namespace Artemis.UI.Shared.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool LoadEventChildren { get; set; } = true;
|
||||||
|
|
||||||
public void ChangeDataModel(DataModelPropertiesViewModel dataModel)
|
public void ChangeDataModel(DataModelPropertiesViewModel dataModel)
|
||||||
{
|
{
|
||||||
if (DataModelViewModel != null)
|
if (DataModelViewModel != null)
|
||||||
@ -197,10 +203,15 @@ namespace Artemis.UI.Shared.Input
|
|||||||
{
|
{
|
||||||
if (!IsDataModelViewModelOpen)
|
if (!IsDataModelViewModelOpen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
UpdateDataModelVisualization();
|
||||||
|
}
|
||||||
|
|
||||||
DataModelViewModel.Update(_dataModelUIService);
|
private void UpdateDataModelVisualization()
|
||||||
|
{
|
||||||
|
DataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren));
|
||||||
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
|
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
|
||||||
extraDataModelViewModel.Update(_dataModelUIService);
|
extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -16,12 +16,21 @@
|
|||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||||
|
<Style x:Key="DataModelConditionButtonCornerToggle" BasedOn="{StaticResource DataModelConditionButton}" TargetType="Button">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding DisplaySwitchButton}" Value="True">
|
||||||
|
<Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="0 2 2 0" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding DisplaySwitchButton}" Value="False">
|
||||||
|
<Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="2" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
<Grid Margin="3 -4">
|
|
||||||
<StackPanel Orientation="Horizontal" Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
|
<StackPanel Orientation="Horizontal" Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
|
||||||
<Button Style="{StaticResource MaterialDesignFlatDarkBgButton}"
|
<Button Style="{StaticResource MaterialDesignFlatDarkBgButton}"
|
||||||
Background="{Binding SwitchButtonBrush}"
|
Background="{Binding SwitchButtonBrush}"
|
||||||
@ -31,15 +40,14 @@
|
|||||||
Width="22"
|
Width="22"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
materialDesign:ButtonAssist.CornerRadius="2 0 0 2"
|
materialDesign:ButtonAssist.CornerRadius="2 0 0 2"
|
||||||
Margin="0 0 -3 0"
|
Margin="3 0 -3 0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
ToolTip="Switch to data model value"
|
ToolTip="Switch to data model value"
|
||||||
Command="{s:Action SwitchToDynamic}"
|
Command="{s:Action SwitchToDynamic}"
|
||||||
Visibility="{Binding DisplaySwitchButton, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
Visibility="{Binding DisplaySwitchButton, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||||
<materialDesign:PackIcon Kind="SwapHorizontal" />
|
<materialDesign:PackIcon Kind="SwapHorizontal" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Style="{StaticResource DataModelConditionButton}"
|
<Button Style="{StaticResource DataModelConditionButtonCornerToggle}"
|
||||||
materialDesign:ButtonAssist.CornerRadius="0 2 2 0"
|
|
||||||
Background="{Binding ButtonBrush}"
|
Background="{Binding ButtonBrush}"
|
||||||
BorderBrush="{Binding ButtonBrush}"
|
BorderBrush="{Binding ButtonBrush}"
|
||||||
Command="{s:Action ActivateInputViewModel}"
|
Command="{s:Action ActivateInputViewModel}"
|
||||||
@ -63,6 +71,7 @@
|
|||||||
Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}}"
|
Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}}"
|
||||||
CornerRadius="3"
|
CornerRadius="3"
|
||||||
Padding="3"
|
Padding="3"
|
||||||
|
Margin="0 -3"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
MinWidth="140">
|
MinWidth="140">
|
||||||
<ContentControl s:View.Model="{Binding InputViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
<ContentControl s:View.Model="{Binding InputViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||||
|
|||||||
@ -69,7 +69,7 @@ namespace Artemis.UI.Shared.Input
|
|||||||
public Type TargetType
|
public Type TargetType
|
||||||
{
|
{
|
||||||
get => _targetType;
|
get => _targetType;
|
||||||
set => SetAndNotify(ref _targetType, value);
|
private set => SetAndNotify(ref _targetType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelPropertyAttribute TargetDescription
|
public DataModelPropertyAttribute TargetDescription
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared
|
||||||
|
{
|
||||||
|
public class DataModelEventViewModel : DataModelVisualizationViewModel
|
||||||
|
{
|
||||||
|
private Type _displayValueType;
|
||||||
|
|
||||||
|
internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type DisplayValueType
|
||||||
|
{
|
||||||
|
get => _displayValueType;
|
||||||
|
set => SetAndNotify(ref _displayValueType, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
|
{
|
||||||
|
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||||
|
|
||||||
|
if (configuration != null)
|
||||||
|
{
|
||||||
|
if (configuration.CreateEventChildren)
|
||||||
|
PopulateProperties(dataModelUIService, configuration);
|
||||||
|
else if (Children.Any())
|
||||||
|
Children.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update children if the parent is expanded
|
||||||
|
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||||
|
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object GetCurrentValue()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DisplayPath ?? Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override int GetChildDepth()
|
||||||
|
{
|
||||||
|
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,8 +15,6 @@ namespace Artemis.UI.Shared
|
|||||||
{
|
{
|
||||||
DataModel = ListPredicateWrapperDataModel.Create(listType);
|
DataModel = ListPredicateWrapperDataModel.Create(listType);
|
||||||
ListType = listType;
|
ListType = listType;
|
||||||
|
|
||||||
IsRootViewModel = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Index
|
public int Index
|
||||||
@ -41,17 +39,17 @@ namespace Artemis.UI.Shared
|
|||||||
|
|
||||||
public override string DisplayPath => null;
|
public override string DisplayPath => null;
|
||||||
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService)
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
{
|
{
|
||||||
((ListPredicateWrapperDataModel) DataModel).UntypedValue = DisplayValue;
|
((ListPredicateWrapperDataModel) DataModel).UntypedValue = DisplayValue;
|
||||||
|
|
||||||
PopulateProperties(dataModelUIService);
|
PopulateProperties(dataModelUIService, configuration);
|
||||||
if (DisplayViewModel == null)
|
if (DisplayViewModel == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (IsVisualizationExpanded && !DisplayViewModel.IsVisualizationExpanded)
|
if (IsVisualizationExpanded && !DisplayViewModel.IsVisualizationExpanded)
|
||||||
DisplayViewModel.IsVisualizationExpanded = IsVisualizationExpanded;
|
DisplayViewModel.IsVisualizationExpanded = IsVisualizationExpanded;
|
||||||
DisplayViewModel.Update(dataModelUIService);
|
DisplayViewModel.Update(dataModelUIService, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object GetCurrentValue()
|
public override object GetCurrentValue()
|
||||||
|
|||||||
@ -39,7 +39,7 @@ namespace Artemis.UI.Shared
|
|||||||
return DisplayValue;
|
return DisplayValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService)
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
{
|
{
|
||||||
// Display value gets updated by parent, don't do anything if it is null
|
// Display value gets updated by parent, don't do anything if it is null
|
||||||
if (DisplayValue == null)
|
if (DisplayValue == null)
|
||||||
|
|||||||
@ -44,32 +44,8 @@ namespace Artemis.UI.Shared
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BindableCollection<DataModelVisualizationViewModel> ListChildren { get; set; }
|
public BindableCollection<DataModelVisualizationViewModel> ListChildren { get; set; }
|
||||||
|
|
||||||
public DataModelPropertiesViewModel GetListTypeViewModel(IDataModelUIService dataModelUIService)
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
{
|
|
||||||
Type listType = DataModelPath.GetPropertyType()?.GetGenericEnumerableType();
|
|
||||||
if (listType == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Create a property VM describing the type of the list
|
|
||||||
DataModelVisualizationViewModel viewModel = CreateListChild(dataModelUIService, listType);
|
|
||||||
viewModel.Update(dataModelUIService);
|
|
||||||
|
|
||||||
// Put an empty value into the list type property view model
|
|
||||||
if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel) return dataModelListClassViewModel;
|
|
||||||
|
|
||||||
if (viewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel)
|
|
||||||
{
|
|
||||||
dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType);
|
|
||||||
DataModelPropertiesViewModel wrapper = new DataModelPropertiesViewModel(null, null, null);
|
|
||||||
wrapper.Children.Add(dataModelListPropertyViewModel);
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService)
|
|
||||||
{
|
{
|
||||||
if (Parent != null && !Parent.IsVisualizationExpanded)
|
if (Parent != null && !Parent.IsVisualizationExpanded)
|
||||||
return;
|
return;
|
||||||
@ -107,7 +83,7 @@ namespace Artemis.UI.Shared
|
|||||||
dataModelListPropertyViewModel.Index = index;
|
dataModelListPropertyViewModel.Index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
child.Update(dataModelUIService);
|
child.Update(dataModelUIService, configuration);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,8 @@ namespace Artemis.UI.Shared
|
|||||||
{
|
{
|
||||||
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
|
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
|
||||||
{
|
{
|
||||||
private Type _displayValueType;
|
|
||||||
private object _displayValue;
|
private object _displayValue;
|
||||||
|
private Type _displayValueType;
|
||||||
|
|
||||||
internal DataModelPropertiesViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
|
internal DataModelPropertiesViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
|
||||||
{
|
{
|
||||||
@ -26,7 +26,7 @@ namespace Artemis.UI.Shared
|
|||||||
set => SetAndNotify(ref _displayValue, value);
|
set => SetAndNotify(ref _displayValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService)
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
{
|
{
|
||||||
DisplayValueType = DataModelPath?.GetPropertyType();
|
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||||
|
|
||||||
@ -37,22 +37,22 @@ namespace Artemis.UI.Shared
|
|||||||
else
|
else
|
||||||
DisplayValue = null;
|
DisplayValue = null;
|
||||||
|
|
||||||
// Always populate properties
|
// Always populate properties
|
||||||
PopulateProperties(dataModelUIService);
|
PopulateProperties(dataModelUIService, configuration);
|
||||||
|
|
||||||
// Only update children if the parent is expanded
|
// Only update children if the parent is expanded
|
||||||
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
|
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||||
dataModelVisualizationViewModel.Update(dataModelUIService);
|
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object GetCurrentValue()
|
public override object GetCurrentValue()
|
||||||
{
|
{
|
||||||
if (Parent == null)
|
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
|
||||||
return null;
|
return DataModel;
|
||||||
return Parent.IsRootViewModel ? DataModel : base.GetCurrentValue();
|
return base.GetCurrentValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -33,7 +33,7 @@ namespace Artemis.UI.Shared
|
|||||||
set => SetAndNotify(ref _displayViewModel, value);
|
set => SetAndNotify(ref _displayViewModel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService)
|
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
{
|
{
|
||||||
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
|
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -29,7 +28,7 @@ namespace Artemis.UI.Shared
|
|||||||
Children = new BindableCollection<DataModelVisualizationViewModel>();
|
Children = new BindableCollection<DataModelVisualizationViewModel>();
|
||||||
IsMatchingFilteredTypes = true;
|
IsMatchingFilteredTypes = true;
|
||||||
|
|
||||||
if (dataModel == null && parent == null && dataModelPath == null)
|
if (parent == null)
|
||||||
IsRootViewModel = true;
|
IsRootViewModel = true;
|
||||||
else
|
else
|
||||||
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription;
|
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription;
|
||||||
@ -88,8 +87,9 @@ namespace Artemis.UI.Shared
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the datamodel and if in an parent, any children
|
/// Updates the datamodel and if in an parent, any children
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataModelUIService"></param>
|
/// <param name="dataModelUIService">The data model UI service used during update</param>
|
||||||
public abstract void Update(IDataModelUIService dataModelUIService);
|
/// <param name="configuration">The configuration to apply while updating</param>
|
||||||
|
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration);
|
||||||
|
|
||||||
public virtual object GetCurrentValue()
|
public virtual object GetCurrentValue()
|
||||||
{
|
{
|
||||||
@ -137,58 +137,24 @@ namespace Artemis.UI.Shared
|
|||||||
if (looseMatch)
|
if (looseMatch)
|
||||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
|
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
|
||||||
t == typeof(Enum) && type.IsEnum ||
|
t == typeof(Enum) && type.IsEnum ||
|
||||||
t == typeof(IEnumerable<>) && type.IsGenericEnumerable());
|
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
|
||||||
|
type.IsGenericType && t == type.GetGenericTypeDefinition());
|
||||||
else
|
else
|
||||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
|
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelVisualizationViewModel GetChildByPath(Guid dataModelGuid, string propertyPath)
|
|
||||||
{
|
|
||||||
if (!IsRootViewModel)
|
|
||||||
{
|
|
||||||
if (DataModel.PluginInfo.Guid != dataModelGuid)
|
|
||||||
return null;
|
|
||||||
if (propertyPath == null)
|
|
||||||
return null;
|
|
||||||
if (Path != null && Path.StartsWith(propertyPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure children are populated by requesting an update
|
|
||||||
if (!IsVisualizationExpanded)
|
|
||||||
{
|
|
||||||
IsVisualizationExpanded = true;
|
|
||||||
RequestUpdate();
|
|
||||||
IsVisualizationExpanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DataModelVisualizationViewModel child in Children)
|
|
||||||
{
|
|
||||||
// Try the child itself first
|
|
||||||
if (child.Path == propertyPath)
|
|
||||||
return child;
|
|
||||||
|
|
||||||
// Try a child on the child next, this will go recursive
|
|
||||||
DataModelVisualizationViewModel match = child.GetChildByPath(dataModelGuid, propertyPath);
|
|
||||||
if (match != null)
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual int GetChildDepth()
|
internal virtual int GetChildDepth()
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PopulateProperties(IDataModelUIService dataModelUIService)
|
internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration dataModelUpdateConfiguration)
|
||||||
{
|
{
|
||||||
if (IsRootViewModel)
|
if (IsRootViewModel && DataModel == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Type modelType = Parent == null || Parent.IsRootViewModel ? DataModel.GetType() : DataModelPath.GetPropertyType();
|
Type modelType = IsRootViewModel ? DataModel.GetType() : DataModelPath.GetPropertyType();
|
||||||
|
|
||||||
// Add missing static children
|
// Add missing static children
|
||||||
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
|
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
|
||||||
{
|
{
|
||||||
@ -219,7 +185,6 @@ namespace Artemis.UI.Shared
|
|||||||
// Add missing dynamic children
|
// Add missing dynamic children
|
||||||
object value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue();
|
object value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue();
|
||||||
if (value is DataModel dataModel)
|
if (value is DataModel dataModel)
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels)
|
foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels)
|
||||||
{
|
{
|
||||||
string childPath = AppendToPath(kvp.Key);
|
string childPath = AppendToPath(kvp.Key);
|
||||||
@ -230,7 +195,6 @@ namespace Artemis.UI.Shared
|
|||||||
if (child != null)
|
if (child != null)
|
||||||
Children.Add(child);
|
Children.Add(child);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Remove dynamic children that have been removed from the data model
|
// Remove dynamic children that have been removed from the data model
|
||||||
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => !c.DataModelPath.IsValid).ToList();
|
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => !c.DataModelPath.IsValid).ToList();
|
||||||
@ -266,6 +230,8 @@ namespace Artemis.UI.Shared
|
|||||||
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
if (propertyType.IsGenericEnumerable())
|
if (propertyType.IsGenericEnumerable())
|
||||||
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
|
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
|
||||||
|
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
// For other value types create a child view model
|
// For other value types create a child view model
|
||||||
if (propertyType.IsClass || propertyType.IsStruct())
|
if (propertyType.IsClass || propertyType.IsStruct())
|
||||||
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
@ -295,4 +261,14 @@ namespace Artemis.UI.Shared
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DataModelUpdateConfiguration
|
||||||
|
{
|
||||||
|
public bool CreateEventChildren { get; }
|
||||||
|
|
||||||
|
public DataModelUpdateConfiguration(bool createEventChildren)
|
||||||
|
{
|
||||||
|
CreateEventChildren = createEventChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared
|
||||||
|
{
|
||||||
|
public static class DataModelWrapperExtensions
|
||||||
|
{
|
||||||
|
public static DataModelPropertiesViewModel CreateViewModel(this EventPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
|
{
|
||||||
|
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(wrapper, null, new DataModelPath(wrapper));
|
||||||
|
viewModel.Update(dataModelUIService, configuration);
|
||||||
|
viewModel.UpdateRequested += (sender, args) => viewModel.Update(dataModelUIService, configuration);
|
||||||
|
viewModel.Children.First().IsVisualizationExpanded = true;
|
||||||
|
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataModelPropertiesViewModel CreateViewModel(this ListPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
|
||||||
|
{
|
||||||
|
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(wrapper, null, new DataModelPath(wrapper));
|
||||||
|
viewModel.Update(dataModelUIService, configuration);
|
||||||
|
viewModel.UpdateRequested += (sender, args) => viewModel.Update(dataModelUIService, configuration);
|
||||||
|
viewModel.Children.First().IsVisualizationExpanded = true;
|
||||||
|
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,6 +39,12 @@
|
|||||||
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center" />
|
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<materialDesign:PackIcon Kind="LightningBolt" VerticalAlignment="Center" Margin="0 0 5 0" />
|
||||||
|
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="Trigger layer on event" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
|
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<!-- Value description -->
|
<!-- Value description -->
|
||||||
@ -73,6 +79,12 @@
|
|||||||
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center"/>
|
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<materialDesign:PackIcon Kind="LightningBolt" VerticalAlignment="Center" Margin="0 0 5 0" />
|
||||||
|
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" ToolTipService.ShowOnDisabled="True" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
|
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
|
||||||
<Grid ToolTip="{Binding PropertyDescription.Description}">
|
<Grid ToolTip="{Binding PropertyDescription.Description}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
|||||||
@ -37,8 +37,8 @@ namespace Artemis.UI.Shared.Services
|
|||||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
viewModel.Update(this);
|
viewModel.Update(this, null);
|
||||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this);
|
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +67,8 @@ namespace Artemis.UI.Shared.Services
|
|||||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, null));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, null));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
viewModel.Update(this);
|
viewModel.Update(this, null);
|
||||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this);
|
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
else
|
else
|
||||||
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
|
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
result.PropertyDescription = description;
|
result.PropertyDescription = description;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -220,8 +220,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
||||||
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
||||||
|
|
||||||
IParameter[] parameters = new IParameter[]
|
IParameter[] parameters = {
|
||||||
{
|
|
||||||
new ConstructorArgument("targetDescription", description),
|
new ConstructorArgument("targetDescription", description),
|
||||||
new ConstructorArgument("initialValue", initialValue)
|
new ConstructorArgument("initialValue", initialValue)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -57,8 +57,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
|
|
||||||
public async Task<bool> ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
|
public async Task<bool> ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
|
||||||
{
|
{
|
||||||
IParameter[] arguments =
|
IParameter[] arguments = {
|
||||||
{
|
|
||||||
new ConstructorArgument("header", header),
|
new ConstructorArgument("header", header),
|
||||||
new ConstructorArgument("text", text),
|
new ConstructorArgument("text", text),
|
||||||
new ConstructorArgument("confirmText", confirmText.ToUpper()),
|
new ConstructorArgument("confirmText", confirmText.ToUpper()),
|
||||||
@ -70,8 +69,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
|
|
||||||
public async Task<bool> ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
|
public async Task<bool> ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string cancelText = "Cancel")
|
||||||
{
|
{
|
||||||
IParameter[] arguments =
|
IParameter[] arguments = {
|
||||||
{
|
|
||||||
new ConstructorArgument("header", header),
|
new ConstructorArgument("header", header),
|
||||||
new ConstructorArgument("text", text),
|
new ConstructorArgument("text", text),
|
||||||
new ConstructorArgument("confirmText", confirmText.ToUpper()),
|
new ConstructorArgument("confirmText", confirmText.ToUpper()),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
internal class ProfileEditorService : IProfileEditorService
|
internal class ProfileEditorService : IProfileEditorService
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||||
private readonly object _selectedProfileElementLock = new object();
|
private readonly object _selectedProfileElementLock = new object();
|
||||||
@ -22,16 +23,23 @@ namespace Artemis.UI.Shared.Services
|
|||||||
private TimeSpan _currentTime;
|
private TimeSpan _currentTime;
|
||||||
private int _pixelsPerSecond;
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger)
|
public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService)
|
||||||
{
|
{
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_coreService = coreService;
|
||||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
|
|
||||||
Kernel = kernel;
|
Kernel = kernel;
|
||||||
PixelsPerSecond = 100;
|
PixelsPerSecond = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||||
|
{
|
||||||
|
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
|
||||||
|
Execute.PostToUIThread(OnProfilePreviewUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
public IKernel Kernel { get; }
|
public IKernel Kernel { get; }
|
||||||
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
||||||
public Profile SelectedProfile { get; private set; }
|
public Profile SelectedProfile { get; private set; }
|
||||||
@ -44,8 +52,8 @@ namespace Artemis.UI.Shared.Services
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_currentTime.Equals(value)) return;
|
if (_currentTime.Equals(value)) return;
|
||||||
if (SelectedProfileElement != null && value > SelectedProfileElement.TimelineLength)
|
if (SelectedProfileElement != null && value > SelectedProfileElement.Timeline.Length)
|
||||||
_currentTime = SelectedProfileElement.TimelineLength;
|
_currentTime = SelectedProfileElement.Timeline.Length;
|
||||||
else
|
else
|
||||||
_currentTime = value;
|
_currentTime = value;
|
||||||
UpdateProfilePreview();
|
UpdateProfilePreview();
|
||||||
@ -142,11 +150,11 @@ namespace Artemis.UI.Shared.Services
|
|||||||
|
|
||||||
// Stick to the main segment for any element that is not currently selected
|
// Stick to the main segment for any element that is not currently selected
|
||||||
foreach (Folder folder in SelectedProfile.GetAllFolders())
|
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())
|
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()
|
public bool UndoUpdateProfile()
|
||||||
@ -224,24 +232,23 @@ namespace Artemis.UI.Shared.Services
|
|||||||
if (snapToSegments)
|
if (snapToSegments)
|
||||||
{
|
{
|
||||||
// Snap to the end of the start segment
|
// Snap to the end of the start segment
|
||||||
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.StartSegmentLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.StartSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
return SelectedProfileElement.StartSegmentLength;
|
return SelectedProfileElement.Timeline.StartSegmentEndPosition;
|
||||||
|
|
||||||
// Snap to the end of the main segment
|
// Snap to the end of the main segment
|
||||||
TimeSpan mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
|
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.MainSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
if (Math.Abs(time.TotalMilliseconds - mainSegmentEnd.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
return SelectedProfileElement.Timeline.MainSegmentEndPosition;
|
||||||
return mainSegmentEnd;
|
|
||||||
|
|
||||||
// Snap to the end of the end segment (end of the timeline)
|
// Snap to the end of the end segment (end of the timeline)
|
||||||
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.TimelineLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
return SelectedProfileElement.TimelineLength;
|
return SelectedProfileElement.Timeline.EndSegmentEndPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapToCurrentTime)
|
if (snapToCurrentTime)
|
||||||
{
|
{
|
||||||
// Snap to the current time
|
// Snap to the current time
|
||||||
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
return SelectedProfileElement.StartSegmentLength;
|
return CurrentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapTimes != null)
|
if (snapTimes != null)
|
||||||
|
|||||||
2
src/Artemis.UI/Artemis.UI.csproj.DotSettings
Normal file
2
src/Artemis.UI/Artemis.UI.csproj.DotSettings
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Cconditions_005Cpredicate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
19
src/Artemis.UI/Converters/ComparisonConverter.cs
Normal file
19
src/Artemis.UI/Converters/ComparisonConverter.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -65,10 +65,12 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
|
|
||||||
public interface IDataModelConditionsVmFactory : IVmFactory
|
public interface IDataModelConditionsVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, bool isListGroup);
|
DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType);
|
||||||
DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList);
|
DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList);
|
||||||
DataModelConditionPredicateViewModel DataModelConditionPredicateViewModel(DataModelConditionPredicate dataModelConditionPredicate);
|
DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent);
|
||||||
|
DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate);
|
||||||
DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate);
|
DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate);
|
||||||
|
DataModelConditionEventPredicateViewModel DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ILayerPropertyVmFactory : IVmFactory
|
public interface ILayerPropertyVmFactory : IVmFactory
|
||||||
|
|||||||
@ -13,12 +13,11 @@ using Stylet;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||||
{
|
{
|
||||||
public class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable
|
public abstract class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IConditionOperatorService _conditionOperatorService;
|
private readonly IConditionOperatorService _conditionOperatorService;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private DataModelDynamicViewModel _leftSideSelectionViewModel;
|
|
||||||
private BindableCollection<BaseConditionOperator> _operators;
|
private BindableCollection<BaseConditionOperator> _operators;
|
||||||
private DataModelStaticViewModel _rightSideInputViewModel;
|
private DataModelStaticViewModel _rightSideInputViewModel;
|
||||||
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
||||||
@ -42,26 +41,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Operators = new BindableCollection<BaseConditionOperator>();
|
Operators = new BindableCollection<BaseConditionOperator>();
|
||||||
|
|
||||||
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
|
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model;
|
public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model;
|
||||||
public PluginSetting<bool> ShowDataModelValues { get; }
|
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||||
|
|
||||||
|
|
||||||
public BindableCollection<BaseConditionOperator> Operators
|
|
||||||
{
|
|
||||||
get => _operators;
|
|
||||||
set => SetAndNotify(ref _operators, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataModelDynamicViewModel LeftSideSelectionViewModel
|
|
||||||
{
|
|
||||||
get => _leftSideSelectionViewModel;
|
|
||||||
set => SetAndNotify(ref _leftSideSelectionViewModel, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseConditionOperator SelectedOperator
|
public BaseConditionOperator SelectedOperator
|
||||||
{
|
{
|
||||||
get => _selectedOperator;
|
get => _selectedOperator;
|
||||||
@ -81,6 +65,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DelegateCommand SelectOperatorCommand { get; }
|
public DelegateCommand SelectOperatorCommand { get; }
|
||||||
|
public BindableCollection<BaseConditionOperator> Operators { get; }
|
||||||
|
|
||||||
|
protected SolidColorBrush LeftSideColor { get; set; }
|
||||||
|
|
||||||
public override void Delete()
|
public override void Delete()
|
||||||
{
|
{
|
||||||
@ -88,15 +75,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public virtual void Initialize()
|
||||||
{
|
{
|
||||||
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
||||||
LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
|
LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected;
|
||||||
|
if (LeftSideColor != null)
|
||||||
|
LeftSideSelectionViewModel.ButtonBrush = LeftSideColor;
|
||||||
|
|
||||||
// Determine which types are currently supported
|
// Determine which types are currently supported
|
||||||
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
_supportedInputTypes = GetSupportedInputTypes();
|
||||||
_supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
|
|
||||||
_supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
|
|
||||||
_supportedInputTypes.Add(typeof(IEnumerable<>));
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +93,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
|
LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
|
||||||
LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath);
|
LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath);
|
||||||
|
|
||||||
Type leftSideType = LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
|
Type leftSideType = GetLeftSideType();
|
||||||
|
|
||||||
// Get the supported operators
|
// Get the supported operators
|
||||||
Operators.Clear();
|
Operators.Clear();
|
||||||
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
|
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
|
||||||
if (DataModelConditionPredicate.Operator == null)
|
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;
|
SelectedOperator = DataModelConditionPredicate.Operator;
|
||||||
|
|
||||||
// Without a selected operator or one that supports a right side, leave the right side input empty
|
// 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();
|
DisposeRightSideDynamicViewModel();
|
||||||
if (RightSideInputViewModel == null)
|
if (RightSideInputViewModel == null)
|
||||||
CreateRightSideInputViewModel(SelectedOperator.RightSideType);
|
CreateRightSideInputViewModel();
|
||||||
|
|
||||||
if (SelectedOperator.RightSideType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
|
Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType();
|
||||||
RightSideInputViewModel.Value = SelectedOperator.RightSideType.GetDefault();
|
// 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
|
else
|
||||||
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
|
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
|
||||||
if (RightSideInputViewModel.TargetType != SelectedOperator.RightSideType)
|
|
||||||
RightSideInputViewModel.UpdateTargetType(SelectedOperator.RightSideType);
|
if (RightSideInputViewModel.TargetType != preferredType)
|
||||||
|
RightSideInputViewModel.UpdateTargetType(preferredType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyLeftSide()
|
public void ApplyLeftSide()
|
||||||
{
|
{
|
||||||
if (LeftSideSelectionViewModel.DataModelPath.GetPropertyType().IsGenericEnumerable())
|
Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType();
|
||||||
{
|
bool converted = ConvertIfRequired(newType);
|
||||||
if (Parent is DataModelConditionGroupViewModel groupViewModel)
|
if (converted)
|
||||||
groupViewModel.ConvertToConditionList(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
|
DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
@ -187,12 +180,20 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract List<Type> GetSupportedInputTypes();
|
||||||
|
protected abstract Type GetLeftSideType();
|
||||||
|
|
||||||
|
protected virtual List<DataModelPropertiesViewModel> GetExtraRightSideDataModelViewModels()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteSelectOperatorCommand(object context)
|
private void ExecuteSelectOperatorCommand(object context)
|
||||||
{
|
{
|
||||||
if (!(context is BaseConditionOperator DataModelConditionOperator))
|
if (!(context is BaseConditionOperator dataModelConditionOperator))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SelectedOperator = DataModelConditionOperator;
|
SelectedOperator = dataModelConditionOperator;
|
||||||
ApplyOperator();
|
ApplyOperator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,11 +223,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
RightSideSelectionViewModel.DisplaySwitchButton = true;
|
RightSideSelectionViewModel.DisplaySwitchButton = true;
|
||||||
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
|
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
|
||||||
RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested;
|
RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested;
|
||||||
|
|
||||||
|
List<DataModelPropertiesViewModel> 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.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush");
|
||||||
RightSideInputViewModel.DisplaySwitchButton = true;
|
RightSideInputViewModel.DisplaySwitchButton = true;
|
||||||
RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
|
RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
|
||||||
@ -278,7 +284,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e)
|
private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
DataModelConditionPredicate.PredicateType = ProfileRightSideType.Dynamic;
|
DataModelConditionPredicate.PredicateType = ProfileRightSideType.Dynamic;
|
||||||
@ -1,10 +1,14 @@
|
|||||||
using Artemis.Core;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Input;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
||||||
{
|
{
|
||||||
public abstract class DataModelConditionViewModel : Conductor<DataModelConditionViewModel>.Collection.AllActive
|
public abstract class DataModelConditionViewModel : Conductor<DataModelConditionViewModel>.Collection.AllActive
|
||||||
{
|
{
|
||||||
|
private DataModelDynamicViewModel _leftSideSelectionViewModel;
|
||||||
|
|
||||||
protected DataModelConditionViewModel(DataModelConditionPart model)
|
protected DataModelConditionViewModel(DataModelConditionPart model)
|
||||||
{
|
{
|
||||||
Model = model;
|
Model = model;
|
||||||
@ -12,6 +16,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
|
|
||||||
public DataModelConditionPart Model { get; }
|
public DataModelConditionPart Model { get; }
|
||||||
|
|
||||||
|
public DataModelDynamicViewModel LeftSideSelectionViewModel
|
||||||
|
{
|
||||||
|
get => _leftSideSelectionViewModel;
|
||||||
|
set => SetAndNotify(ref _leftSideSelectionViewModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void Update();
|
public abstract void Update();
|
||||||
|
|
||||||
public virtual void Delete()
|
public virtual void Delete()
|
||||||
@ -19,5 +29,29 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract
|
|||||||
Model.Parent.RemoveChild(Model);
|
Model.Parent.RemoveChild(Model);
|
||||||
((DataModelConditionViewModel) Parent).Update();
|
((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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionEventView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
|
||||||
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
ToolTip="Delete the event trigger"
|
||||||
|
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="#E74C4C"
|
||||||
|
Width="25"
|
||||||
|
Height="25"
|
||||||
|
Command="{s:Action Delete}">
|
||||||
|
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Left side, the list this predicate is targeting -->
|
||||||
|
<ContentControl Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
s:View.Model="{Binding LeftSideSelectionViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5 0 0 0">
|
||||||
|
triggered
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Items}" Margin="0 3 0 0">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -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<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
|
List<Type> supportedInputTypes = new List<Type> {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<DataModelConditionViewModel> viewModels = new List<DataModelConditionViewModel>();
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@
|
|||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||||
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
@ -45,13 +46,24 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Row="0"
|
<Button Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
ToolTip="Change the operator of the group, determining which conditions should match"
|
|
||||||
Style="{StaticResource DataModelConditionButtonLeftClickMenu}"
|
Style="{StaticResource DataModelConditionButtonLeftClickMenu}"
|
||||||
Background="#E74C4C"
|
Background="#E74C4C"
|
||||||
BorderBrush="#E74C4C"
|
BorderBrush="#E74C4C"
|
||||||
Margin="3 1"
|
Margin="3 1"
|
||||||
Content="{Binding SelectedBooleanOperator}"
|
Content="{Binding SelectedBooleanOperator}"
|
||||||
Visibility="{Binding DisplayBooleanOperator, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
Visibility="{Binding DisplayBooleanOperator, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||||
|
IsEnabled="{Binding IsEventGroup, Converter={StaticResource InverseBooleanConverter}}"
|
||||||
|
ToolTipService.ShowOnDisabled="True">
|
||||||
|
<Button.ToolTip>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Visibility="{Binding IsEventGroup, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||||
|
Change the operator of the group, determining which conditions should match
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Visibility="{Binding IsEventGroup, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||||
|
The operator of the root group cannot be changed for event-based conditions
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button.ToolTip>
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="And"
|
<MenuItem Header="And"
|
||||||
@ -98,12 +110,20 @@
|
|||||||
</Button.Style>
|
</Button.Style>
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Add condition" ToolTip="A condition that compares with another value" Command="{s:Action AddCondition}">
|
<MenuItem Header="Add condition" ToolTip="A condition that evaluates the state of a property in the data model" Command="{s:Action AddCondition}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="Equal" />
|
<materialDesign:PackIcon Kind="Equal" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Add group" ToolTip="A group can contain conditions and other groups" Command="{s:Action AddGroup}">
|
<MenuItem Header="Add event condition"
|
||||||
|
ToolTip="An event condition that responds to data model events"
|
||||||
|
Command="{s:Action AddEventCondition}"
|
||||||
|
Visibility="{Binding Data.CanAddEventCondition, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="LightningBolt" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Add group" ToolTip="A group for conditions and other groups" Command="{s:Action AddGroup}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="CodeParentheses" />
|
<materialDesign:PackIcon Kind="CodeParentheses" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
@ -113,7 +133,7 @@
|
|||||||
<materialDesign:PackIcon Kind="Add" Width="18" Height="18" />
|
<materialDesign:PackIcon Kind="Add" Width="18" Height="18" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Items, IsAsync=True}">
|
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Items, IsAsync=True}" Margin="0 3 0 0">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||||
|
|||||||
@ -19,14 +19,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
private bool _isRootGroup;
|
private bool _isRootGroup;
|
||||||
|
private bool _isEventGroup;
|
||||||
|
|
||||||
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
|
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
|
||||||
bool isListGroup,
|
ConditionGroupType groupType,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
||||||
: base(dataModelConditionGroup)
|
: base(dataModelConditionGroup)
|
||||||
{
|
{
|
||||||
IsListGroup = isListGroup;
|
GroupType = groupType;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
_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 DataModelConditionGroup DataModelConditionGroup => (DataModelConditionGroup) Model;
|
||||||
|
|
||||||
public bool IsRootGroup
|
public bool IsRootGroup
|
||||||
{
|
{
|
||||||
get => _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
|
public bool IsInitialized
|
||||||
@ -68,10 +81,37 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
public void AddCondition()
|
public void AddCondition()
|
||||||
{
|
{
|
||||||
if (!IsListGroup)
|
switch (GroupType)
|
||||||
DataModelConditionGroup.AddChild(new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
|
{
|
||||||
else
|
case ConditionGroupType.General:
|
||||||
DataModelConditionGroup.AddChild(new DataModelConditionListPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic));
|
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();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
@ -88,11 +128,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
|
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
|
||||||
|
|
||||||
// Remove VMs of effects no longer applied on the layer
|
// Remove VMs of effects no longer applied on the layer
|
||||||
Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList());
|
Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList());
|
||||||
|
|
||||||
List<DataModelConditionViewModel> viewModels = new List<DataModelConditionViewModel>();
|
|
||||||
foreach (DataModelConditionPart childModel in Model.Children)
|
foreach (DataModelConditionPart childModel in Model.Children)
|
||||||
{
|
{
|
||||||
if (Items.Any(c => c.Model == childModel))
|
if (Items.Any(c => c.Model == childModel))
|
||||||
@ -100,39 +138,43 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
switch (childModel)
|
switch (childModel)
|
||||||
{
|
{
|
||||||
case DataModelConditionGroup DataModelConditionGroup:
|
case DataModelConditionGroup dataModelConditionGroup:
|
||||||
viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataModelConditionGroup, IsListGroup));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, GroupType));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionList DataModelConditionListPredicate:
|
case DataModelConditionList dataModelConditionList:
|
||||||
viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(DataModelConditionListPredicate));
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(dataModelConditionList));
|
||||||
break;
|
break;
|
||||||
case DataModelConditionPredicate DataModelConditionPredicate:
|
case DataModelConditionEvent dataModelConditionEvent:
|
||||||
if (!IsListGroup)
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent));
|
||||||
viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionPredicateViewModel(DataModelConditionPredicate));
|
|
||||||
break;
|
break;
|
||||||
case DataModelConditionListPredicate DataModelConditionListPredicate:
|
case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate:
|
||||||
if (IsListGroup)
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate));
|
||||||
viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(DataModelConditionListPredicate));
|
break;
|
||||||
|
case DataModelConditionListPredicate dataModelConditionListPredicate:
|
||||||
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(dataModelConditionListPredicate));
|
||||||
|
break;
|
||||||
|
case DataModelConditionEventPredicate dataModelConditionEventPredicate:
|
||||||
|
Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventPredicateViewModel(dataModelConditionEventPredicate));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModels.Any())
|
|
||||||
Items.AddRange(viewModels);
|
|
||||||
|
|
||||||
// Ensure the items are in the same order as on the model
|
// Ensure the items are in the same order as on the model
|
||||||
((BindableCollection<DataModelConditionViewModel>) Items).Sort(i => Model.Children.IndexOf(i.Model));
|
((BindableCollection<DataModelConditionViewModel>) Items).Sort(i => Model.Children.IndexOf(i.Model));
|
||||||
|
|
||||||
foreach (DataModelConditionViewModel childViewModel in Items)
|
foreach (DataModelConditionViewModel childViewModel in Items)
|
||||||
childViewModel.Update();
|
childViewModel.Update();
|
||||||
|
|
||||||
if (IsRootGroup && Parent is DisplayConditionsViewModel displayConditionsViewModel)
|
IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel);
|
||||||
displayConditionsViewModel.DisplayStartHint = !Items.Any();
|
if (IsEventGroup)
|
||||||
|
{
|
||||||
|
if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And)
|
||||||
|
SelectBooleanOperator("And");
|
||||||
|
}
|
||||||
|
|
||||||
OnUpdated();
|
OnUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertToConditionList(DataModelConditionPredicateViewModel predicateViewModel)
|
public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel)
|
||||||
{
|
{
|
||||||
// Store the old index and remove the old predicate
|
// Store the old index and remove the old predicate
|
||||||
int index = DataModelConditionGroup.Children.IndexOf(predicateViewModel.Model);
|
int index = DataModelConditionGroup.Children.IndexOf(predicateViewModel.Model);
|
||||||
@ -147,15 +189,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertToPredicate(DataModelConditionListViewModel listViewModel)
|
public void ConvertToPredicate(DataModelConditionViewModel listViewModel)
|
||||||
{
|
{
|
||||||
// Store the old index and remove the old predicate
|
// Store the old index and remove the old predicate
|
||||||
int index = DataModelConditionGroup.Children.IndexOf(listViewModel.Model);
|
int index = DataModelConditionGroup.Children.IndexOf(listViewModel.Model);
|
||||||
DataModelConditionGroup.RemoveChild(listViewModel.Model);
|
DataModelConditionGroup.RemoveChild(listViewModel.Model);
|
||||||
|
|
||||||
// Insert a list in the same position
|
// Insert a list in the same position
|
||||||
DataModelConditionPredicate predicate = new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic);
|
DataModelConditionGeneralPredicate predicate = new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic);
|
||||||
predicate.UpdateLeftSide(listViewModel.TargetSelectionViewModel.DataModelPath);
|
predicate.UpdateLeftSide(listViewModel.LeftSideSelectionViewModel.DataModelPath);
|
||||||
DataModelConditionGroup.AddChild(predicate, index);
|
DataModelConditionGroup.AddChild(predicate, index);
|
||||||
|
|
||||||
// Update to switch the VMs
|
// Update to switch the VMs
|
||||||
@ -169,4 +211,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
Updated?.Invoke(this, EventArgs.Empty);
|
Updated?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ConditionGroupType
|
||||||
|
{
|
||||||
|
General,
|
||||||
|
List,
|
||||||
|
Event
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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<BaseConditionOperator> _operators;
|
|
||||||
private DataModelStaticViewModel _rightSideInputViewModel;
|
|
||||||
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
|
||||||
private BaseConditionOperator _selectedOperator;
|
|
||||||
|
|
||||||
private List<Type> _supportedInputTypes;
|
|
||||||
|
|
||||||
public DataModelConditionListPredicateViewModel(
|
|
||||||
DataModelConditionListPredicate dataModelConditionListPredicate,
|
|
||||||
IProfileEditorService profileEditorService,
|
|
||||||
IDataModelUIService dataModelUIService,
|
|
||||||
IConditionOperatorService conditionOperatorService) : base(dataModelConditionListPredicate)
|
|
||||||
{
|
|
||||||
_profileEditorService = profileEditorService;
|
|
||||||
_dataModelUIService = dataModelUIService;
|
|
||||||
_conditionOperatorService = conditionOperatorService;
|
|
||||||
_supportedInputTypes = new List<Type>();
|
|
||||||
|
|
||||||
SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand);
|
|
||||||
Operators = new BindableCollection<BaseConditionOperator>();
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model;
|
|
||||||
|
|
||||||
public BindableCollection<BaseConditionOperator> 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<DataModelVisualizationRegistration> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid Margin="0 3">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<!-- Left side, the list this predicate is targeting -->
|
<!-- Left side, the list this predicate is targeting -->
|
||||||
<ContentControl Grid.Row="0"
|
<ContentControl Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
s:View.Model="{Binding TargetSelectionViewModel}"
|
s:View.Model="{Binding LeftSideSelectionViewModel}"
|
||||||
VerticalContentAlignment="Stretch"
|
VerticalContentAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
IsTabStop="False" />
|
IsTabStop="False" />
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
@ -7,7 +6,6 @@ using Artemis.Core;
|
|||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Input;
|
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
||||||
@ -18,7 +16,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
||||||
private readonly IDataModelUIService _dataModelUIService;
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private DataModelDynamicViewModel _targetSelectionViewModel;
|
|
||||||
|
|
||||||
public DataModelConditionListViewModel(
|
public DataModelConditionListViewModel(
|
||||||
DataModelConditionList dataModelConditionList,
|
DataModelConditionList dataModelConditionList,
|
||||||
@ -29,26 +26,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_dataModelUIService = dataModelUIService;
|
_dataModelUIService = dataModelUIService;
|
||||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataModelDynamicViewModel TargetSelectionViewModel
|
|
||||||
{
|
|
||||||
get => _targetSelectionViewModel;
|
|
||||||
set => SetAndNotify(ref _targetSelectionViewModel, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelConditionList DataModelConditionList => (DataModelConditionList) Model;
|
public DataModelConditionList DataModelConditionList => (DataModelConditionList) Model;
|
||||||
|
|
||||||
public string SelectedListOperator => DataModelConditionList.ListOperator.Humanize();
|
public string SelectedListOperator => DataModelConditionList.ListOperator.Humanize();
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
TargetSelectionViewModel.Dispose();
|
|
||||||
TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SelectListOperator(string type)
|
public void SelectListOperator(string type)
|
||||||
{
|
{
|
||||||
ListOperator enumValue = Enum.Parse<ListOperator>(type);
|
ListOperator enumValue = Enum.Parse<ListOperator>(type);
|
||||||
@ -60,7 +43,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
public void AddCondition()
|
public void AddCondition()
|
||||||
{
|
{
|
||||||
DataModelConditionList.AddChild(new DataModelConditionPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
|
DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic));
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
@ -82,38 +65,37 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
|
||||||
TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected;
|
LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected;
|
||||||
|
|
||||||
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
List<Type> supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
|
List<Type> supportedInputTypes = editors.Select(e => e.SupportedType).ToList();
|
||||||
supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
|
supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes));
|
||||||
supportedInputTypes.Add(typeof(IEnumerable<>));
|
supportedInputTypes.Add(typeof(IEnumerable<>));
|
||||||
TargetSelectionViewModel.FilterTypes = supportedInputTypes.ToArray();
|
|
||||||
|
|
||||||
TargetSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
|
LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray();
|
||||||
TargetSelectionViewModel.Placeholder = "Select a list";
|
LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
|
||||||
|
LeftSideSelectionViewModel.Placeholder = "Select a list";
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyList()
|
public void ApplyList()
|
||||||
{
|
{
|
||||||
if (!TargetSelectionViewModel.DataModelPath.GetPropertyType().IsGenericEnumerable())
|
Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType();
|
||||||
{
|
bool converted = ConvertIfRequired(newType);
|
||||||
if (Parent is DataModelConditionGroupViewModel groupViewModel)
|
if (converted)
|
||||||
groupViewModel.ConvertToPredicate(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
DataModelConditionList.UpdateList(TargetSelectionViewModel.DataModelPath);
|
DataModelConditionList.UpdateList(LeftSideSelectionViewModel.DataModelPath);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
TargetSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath);
|
LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath);
|
||||||
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
||||||
|
|
||||||
// Remove VMs of effects no longer applied on the layer
|
// 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))
|
if (!(childModel is DataModelConditionGroup dataModelConditionGroup))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, true);
|
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.List);
|
||||||
viewModel.IsRootGroup = true;
|
viewModel.IsRootGroup = true;
|
||||||
viewModels.Add(viewModel);
|
viewModels.Add(viewModel);
|
||||||
}
|
}
|
||||||
@ -142,9 +124,21 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
|||||||
childViewModel.Update();
|
childViewModel.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TargetSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
|
protected override void OnInitialActivate()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
base.OnInitialActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LeftSideSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
|
||||||
{
|
{
|
||||||
ApplyList();
|
ApplyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
LeftSideSelectionViewModel.Dispose();
|
||||||
|
LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
<UserControl
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
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:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||||
|
xmlns:DataModelConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionEventPredicateView"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type DataModelConditions:DataModelConditionEventPredicateViewModel}}">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
|
<ResourceDictionary>
|
||||||
|
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid Margin="0 0 0 3">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
ToolTip="Delete the predicate"
|
||||||
|
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="#E74C4C"
|
||||||
|
Width="25"
|
||||||
|
Height="25"
|
||||||
|
Command="{s:Action Delete}">
|
||||||
|
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Left side -->
|
||||||
|
<ContentControl Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
s:View.Model="{Binding LeftSideSelectionViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
|
||||||
|
<!-- Operator -->
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Style="{StaticResource DataModelConditionButtonLeftClickMenu}"
|
||||||
|
Background="#7B7B7B"
|
||||||
|
BorderBrush="#7B7B7B"
|
||||||
|
Content="{Binding SelectedOperator.Description}"
|
||||||
|
Click="PropertyButton_OnClick">
|
||||||
|
<Button.ContextMenu>
|
||||||
|
<ContextMenu ItemsSource="{Binding Operators}">
|
||||||
|
<ContextMenu.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<materialDesign:PackIcon Kind="{Binding Icon}" VerticalAlignment="Center" Margin="0 0 15 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ContextMenu.ItemTemplate>
|
||||||
|
<ContextMenu.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||||
|
<Setter Property="Command" Value="{Binding Data.SelectOperatorCommand, Source={StaticResource DataContextProxy}}" />
|
||||||
|
<Setter Property="CommandParameter" Value="{Binding}" />
|
||||||
|
<Setter Property="CommandTarget" Value="{Binding}" />
|
||||||
|
</Style>
|
||||||
|
</ContextMenu.ItemContainerStyle>
|
||||||
|
</ContextMenu>
|
||||||
|
</Button.ContextMenu>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Right side, either a selection or an input -->
|
||||||
|
<ContentControl Grid.Row="0"
|
||||||
|
Grid.Column="3"
|
||||||
|
s:View.Model="{Binding RightSideSelectionViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
<ContentControl Grid.Row="0"
|
||||||
|
Grid.Column="3"
|
||||||
|
s:View.Model="{Binding RightSideInputViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -4,11 +4,11 @@ using System.Windows.Controls;
|
|||||||
namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for DataModelConditionPredicateView.xaml
|
/// Interaction logic for DataModelConditionEventPredicateView.xaml
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DataModelConditionPredicateView : UserControl
|
public partial class DataModelConditionEventPredicateView : UserControl
|
||||||
{
|
{
|
||||||
public DataModelConditionPredicateView()
|
public DataModelConditionEventPredicateView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@ -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<Type> GetSupportedInputTypes()
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
|
List<Type> 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<DataModelPropertiesViewModel> GetExtraRightSideDataModelViewModels()
|
||||||
|
{
|
||||||
|
// Extra data models are expected to not have an empty root, so lets return the child
|
||||||
|
return GetEventDataModel().Children.Cast<DataModelPropertiesViewModel>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataModelPropertiesViewModel GetEventDataModel()
|
||||||
|
{
|
||||||
|
EventPredicateWrapperDataModel wrapper = EventPredicateWrapperDataModel.Create(
|
||||||
|
DataModelConditionEventPredicate.DataModelConditionEvent.EventArgumentType
|
||||||
|
);
|
||||||
|
|
||||||
|
return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,10 +7,10 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||||
xmlns:conditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
|
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"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
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}}">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid Margin="0 3">
|
<Grid Margin="0 0 0 3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for DataModelConditionGeneralPredicateView.xaml
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Type> GetSupportedInputTypes()
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
|
List<Type> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,7 +5,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
|
||||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||||
xmlns:DataModelConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
|
xmlns:DataModelConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionListPredicateView"
|
x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionListPredicateView"
|
||||||
@ -17,21 +16,12 @@
|
|||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
|
||||||
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||||
<DataTemplate x:Key="DataModelDataTemplate">
|
|
||||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
|
|
||||||
<DataTemplate.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Data.ShowDataModelValues.Value, Source={StaticResource DataContextProxy}}" Value="True">
|
|
||||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
|
|
||||||
</DataTrigger>
|
|
||||||
</DataTemplate.Triggers>
|
|
||||||
</DataTemplate>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid Margin="0 3">
|
<Grid Margin="0 0 0 3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
@ -50,7 +40,7 @@
|
|||||||
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
|
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<!-- Left side, may be null for primitive lists -->
|
<!-- Left side, always a property -->
|
||||||
<ContentControl Grid.Row="0"
|
<ContentControl Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
s:View.Model="{Binding LeftSideSelectionViewModel}"
|
s:View.Model="{Binding LeftSideSelectionViewModel}"
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
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 DataModelConditionListPredicateViewModel : DataModelConditionPredicateViewModel
|
||||||
|
{
|
||||||
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
|
||||||
|
public DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate,
|
||||||
|
IProfileEditorService profileEditorService,
|
||||||
|
IDataModelUIService dataModelUIService,
|
||||||
|
IConditionOperatorService conditionOperatorService,
|
||||||
|
ISettingsService settingsService)
|
||||||
|
: base(dataModelConditionListPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
|
||||||
|
{
|
||||||
|
_dataModelUIService = dataModelUIService;
|
||||||
|
|
||||||
|
LeftSideColor = new SolidColorBrush(Color.FromRgb(71, 108, 188));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataModelConditionListPredicate DataModelConditionListPredicate => (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<Type> GetSupportedInputTypes()
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<DataModelVisualizationRegistration> editors = _dataModelUIService.RegisteredDataModelEditors;
|
||||||
|
List<Type> 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<DataModelPropertiesViewModel> GetExtraRightSideDataModelViewModels()
|
||||||
|
{
|
||||||
|
if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue)
|
||||||
|
return new List<DataModelPropertiesViewModel> {listValue};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataModelPropertiesViewModel GetListDataModel()
|
||||||
|
{
|
||||||
|
ListPredicateWrapperDataModel wrapper = ListPredicateWrapperDataModel.Create(
|
||||||
|
DataModelConditionListPredicate.DataModelConditionList.ListType
|
||||||
|
);
|
||||||
|
|
||||||
|
return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,12 +7,14 @@
|
|||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
|
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"
|
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}">
|
d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||||
|
<converters:ComparisonConverter x:Key="ComparisonConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
@ -56,9 +58,9 @@
|
|||||||
</materialDesign:Card>
|
</materialDesign:Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="3">
|
<Grid Grid.Row="3" Visibility="{Binding IsEventCondition, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="20" />
|
<RowDefinition Height="18" />
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@ -67,19 +69,15 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Play mode -->
|
<!-- Play mode -->
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
<TextBlock Grid.Column="0" Text="Play mode">
|
||||||
<materialDesign:PackIcon Kind="PlayOutline" VerticalAlignment="Center" />
|
<TextBlock.ToolTip>
|
||||||
<TextBlock Text="Play mode" VerticalAlignment="Center">
|
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||||
<TextBlock.ToolTip>
|
<TextBlock>
|
||||||
<ToolTip Placement="Center" VerticalOffset="-30">
|
Configure how the layer should act while the conditions above are met
|
||||||
<TextBlock>
|
</TextBlock>
|
||||||
Configure how the layer should act while the conditions above are met
|
</ToolTip>
|
||||||
</TextBlock>
|
</TextBlock.ToolTip>
|
||||||
</ToolTip>
|
</TextBlock>
|
||||||
</TextBlock.ToolTip>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<materialDesign:ColorZone Grid.Row="1" Grid.Column="0" Mode="Standard" CornerRadius="3" Margin="0 0 2 0">
|
<materialDesign:ColorZone Grid.Row="1" Grid.Column="0" Mode="Standard" CornerRadius="3" Margin="0 0 2 0">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@ -98,10 +96,10 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</RadioButton.ToolTip>
|
</RadioButton.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" />
|
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
<TextBlock FontSize="12" VerticalAlignment="Center">REPEAT</TextBlock>
|
REPEAT
|
||||||
</StackPanel>
|
</TextBlock>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton Grid.Column="1"
|
<RadioButton Grid.Column="1"
|
||||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||||
@ -115,27 +113,24 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</RadioButton.ToolTip>
|
</RadioButton.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
<materialDesign:PackIcon Kind="StopwatchOutline" VerticalAlignment="Center" />
|
<materialDesign:PackIcon Kind="StopwatchOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
<TextBlock FontSize="12" VerticalAlignment="Center">ONCE</TextBlock>
|
ONCE
|
||||||
</StackPanel>
|
</TextBlock>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
</materialDesign:ColorZone>
|
</materialDesign:ColorZone>
|
||||||
|
|
||||||
<!-- Stop mode -->
|
<!-- Stop mode -->
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
<TextBlock Grid.Row="0" Grid.Column="1" Text="Stop mode">
|
||||||
<materialDesign:PackIcon Kind="Stop" VerticalAlignment="Center" />
|
<TextBlock.ToolTip>
|
||||||
<TextBlock Text="Stop mode" VerticalAlignment="Center">
|
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||||
<TextBlock.ToolTip>
|
<TextBlock>
|
||||||
<ToolTip Placement="Center" VerticalOffset="-30">
|
Configure how the layer should act when the conditions above are no longer met
|
||||||
<TextBlock>
|
</TextBlock>
|
||||||
Configure how the layer should act when the conditions above are no longer met
|
</ToolTip>
|
||||||
</TextBlock>
|
</TextBlock.ToolTip>
|
||||||
</ToolTip>
|
</TextBlock>
|
||||||
</TextBlock.ToolTip>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
<materialDesign:ColorZone Grid.Row="1" Grid.Column="1" Mode="Standard" CornerRadius="3" Margin="2 0 0 0" HorizontalAlignment="Stretch">
|
<materialDesign:ColorZone Grid.Row="1" Grid.Column="1" Mode="Standard" CornerRadius="3" Margin="2 0 0 0" HorizontalAlignment="Stretch">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@ -154,10 +149,10 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</RadioButton.ToolTip>
|
</RadioButton.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
<materialDesign:PackIcon Kind="PlayOutline" VerticalAlignment="Center" />
|
<materialDesign:PackIcon Kind="PlayOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
<TextBlock FontSize="12" VerticalAlignment="Center">FINISH</TextBlock>
|
FINISH
|
||||||
</StackPanel>
|
</TextBlock>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton Grid.Column="1"
|
<RadioButton Grid.Column="1"
|
||||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||||
@ -171,10 +166,82 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</RadioButton.ToolTip>
|
</RadioButton.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
<materialDesign:PackIcon Kind="SkipNextOutline" VerticalAlignment="Center" />
|
<materialDesign:PackIcon Kind="SkipNextOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
<TextBlock FontSize="12" VerticalAlignment="Center">SKIP TO END</TextBlock>
|
SKIP TO END
|
||||||
</StackPanel>
|
</TextBlock>
|
||||||
|
</RadioButton>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:ColorZone>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="3" Visibility="{Binding IsEventCondition, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="18" />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Trigger mode -->
|
||||||
|
<TextBlock Grid.Column="0" Text="Rapid trigger mode">
|
||||||
|
<TextBlock.ToolTip>
|
||||||
|
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||||
|
<TextBlock>
|
||||||
|
Configure how the layer should act when the event(s) trigger before the timeline finishes
|
||||||
|
</TextBlock>
|
||||||
|
</ToolTip>
|
||||||
|
</TextBlock.ToolTip>
|
||||||
|
</TextBlock>
|
||||||
|
<materialDesign:ColorZone Grid.Row="1" Grid.Column="0" Mode="Standard" CornerRadius="3">
|
||||||
|
<Grid HorizontalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<RadioButton Grid.Column="0"
|
||||||
|
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||||
|
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
|
RESTART
|
||||||
|
</TextBlock>
|
||||||
|
<RadioButton.ToolTip>
|
||||||
|
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||||
|
<TextBlock>
|
||||||
|
Stop the current run and restart the timeline
|
||||||
|
</TextBlock>
|
||||||
|
</ToolTip>
|
||||||
|
</RadioButton.ToolTip>
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton Grid.Column="1"
|
||||||
|
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||||
|
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
|
IGNORE
|
||||||
|
</TextBlock>
|
||||||
|
<RadioButton.ToolTip>
|
||||||
|
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||||
|
<TextBlock>
|
||||||
|
Ignore subsequent event fires until the timeline finishes
|
||||||
|
</TextBlock>
|
||||||
|
</ToolTip>
|
||||||
|
</RadioButton.ToolTip>
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton Grid.Column="2"
|
||||||
|
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||||
|
IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||||
|
COPY
|
||||||
|
</TextBlock>
|
||||||
|
<RadioButton.ToolTip>
|
||||||
|
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||||
|
<TextBlock>
|
||||||
|
Play another copy of the timeline on top of the current run
|
||||||
|
</TextBlock>
|
||||||
|
</ToolTip>
|
||||||
|
</RadioButton.ToolTip>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
</materialDesign:ColorZone>
|
</materialDesign:ColorZone>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
||||||
@ -14,6 +15,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private RenderProfileElement _renderProfileElement;
|
private RenderProfileElement _renderProfileElement;
|
||||||
private bool _displayStartHint;
|
private bool _displayStartHint;
|
||||||
|
private bool _isEventCondition;
|
||||||
|
|
||||||
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDataModelConditionsVmFactory dataModelConditionsVmFactory)
|
||||||
{
|
{
|
||||||
@ -27,6 +29,12 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
set => SetAndNotify(ref _displayStartHint, value);
|
set => SetAndNotify(ref _displayStartHint, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsEventCondition
|
||||||
|
{
|
||||||
|
get => _isEventCondition;
|
||||||
|
set => SetAndNotify(ref _isEventCondition, value);
|
||||||
|
}
|
||||||
|
|
||||||
public RenderProfileElement RenderProfileElement
|
public RenderProfileElement RenderProfileElement
|
||||||
{
|
{
|
||||||
get => _renderProfileElement;
|
get => _renderProfileElement;
|
||||||
@ -35,22 +43,24 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
|
|
||||||
public bool DisplayContinuously
|
public bool DisplayContinuously
|
||||||
{
|
{
|
||||||
get => RenderProfileElement?.DisplayContinuously ?? false;
|
get => RenderProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (RenderProfileElement == null || RenderProfileElement.DisplayContinuously == value) return;
|
TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
|
||||||
RenderProfileElement.DisplayContinuously = value;
|
if (RenderProfileElement == null || RenderProfileElement?.Timeline.PlayMode == playMode) return;
|
||||||
|
RenderProfileElement.Timeline.PlayMode = playMode;
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AlwaysFinishTimeline
|
public bool AlwaysFinishTimeline
|
||||||
{
|
{
|
||||||
get => RenderProfileElement?.AlwaysFinishTimeline ?? false;
|
get => RenderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (RenderProfileElement == null || RenderProfileElement.AlwaysFinishTimeline == value) return;
|
TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd;
|
||||||
RenderProfileElement.AlwaysFinishTimeline = value;
|
if (RenderProfileElement == null || RenderProfileElement?.Timeline.StopMode == stopMode) return;
|
||||||
|
RenderProfileElement.Timeline.StopMode = stopMode;
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +81,14 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
|
|
||||||
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (RenderProfileElement != null)
|
||||||
|
{
|
||||||
|
RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
|
||||||
|
RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
|
||||||
|
}
|
||||||
|
|
||||||
RenderProfileElement = e.RenderProfileElement;
|
RenderProfileElement = e.RenderProfileElement;
|
||||||
|
|
||||||
NotifyOfPropertyChange(nameof(DisplayContinuously));
|
NotifyOfPropertyChange(nameof(DisplayContinuously));
|
||||||
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
|
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
|
||||||
NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled));
|
NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled));
|
||||||
@ -86,12 +103,21 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
if (e.RenderProfileElement.DisplayCondition == null)
|
if (e.RenderProfileElement.DisplayCondition == null)
|
||||||
e.RenderProfileElement.DisplayCondition = new DataModelConditionGroup(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.IsRootGroup = true;
|
||||||
ActiveItem.Update();
|
ActiveItem.Update();
|
||||||
|
|
||||||
// Only show the intro to conditions once, and only if the layer has no conditions
|
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||||
DisplayStartHint = !ActiveItem.Items.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,18 +5,13 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
xmlns:dd="urn:gong-wpf-dragdrop" xmlns:Converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:dd="urn:gong-wpf-dragdrop"
|
||||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||||
<ResourceDictionary>
|
|
||||||
<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
|
||||||
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
@ -27,43 +22,16 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Button Grid.Row="0"
|
<Button Grid.Row="0"
|
||||||
Style="{StaticResource DataModelConditionButtonLeftClickMenu}"
|
Style="{StaticResource DataModelConditionButton}"
|
||||||
Background="{StaticResource PrimaryHueMidBrush}"
|
Background="{StaticResource PrimaryHueMidBrush}"
|
||||||
BorderBrush="{StaticResource PrimaryHueMidBrush}"
|
BorderBrush="{StaticResource PrimaryHueMidBrush}"
|
||||||
HorizontalAlignment="Right">
|
HorizontalAlignment="Right"
|
||||||
|
Command="{s:Action AddCondition}">
|
||||||
ADD CONDITION
|
ADD CONDITION
|
||||||
<Button.ContextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<MenuItem Header="Add static condition"
|
|
||||||
ToolTip="A condition that compares with a static input"
|
|
||||||
Command="{s:Action AddCondition}"
|
|
||||||
CommandParameter="Static">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="FormTextarea" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Add dynamic condition"
|
|
||||||
ToolTip="A condition that compares with a data model property"
|
|
||||||
Command="{s:Action AddCondition}"
|
|
||||||
CommandParameter="Dynamic">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="Link" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Add list condition"
|
|
||||||
ToolTip="A condition that evaluates on items in a list"
|
|
||||||
Command="{s:Action AddCondition}"
|
|
||||||
CommandParameter="List">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="FormatListBulleted" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
</ContextMenu>
|
|
||||||
</Button.ContextMenu>
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ListBox Grid.Row="1"
|
<ListBox Grid.Row="1"
|
||||||
ItemsSource="{Binding ConditionViewModels}"
|
ItemsSource="{Binding Items}"
|
||||||
materialDesign:RippleAssist.IsDisabled="True"
|
materialDesign:RippleAssist.IsDisabled="True"
|
||||||
dd:DragDrop.IsDragSource="True"
|
dd:DragDrop.IsDragSource="True"
|
||||||
dd:DragDrop.IsDropTarget="True"
|
dd:DragDrop.IsDropTarget="True"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ using Stylet;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
|
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
|
||||||
{
|
{
|
||||||
public class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Screen, IDataBindingModeViewModel
|
public class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Conductor<DataBindingConditionViewModel<TLayerProperty, TProperty>>.Collection.AllActive, IDataBindingModeViewModel
|
||||||
{
|
{
|
||||||
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
|
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
@ -24,18 +24,83 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
|
|||||||
_dataBindingsVmFactory = dataBindingsVmFactory;
|
_dataBindingsVmFactory = dataBindingsVmFactory;
|
||||||
|
|
||||||
ConditionalDataBinding = conditionalDataBinding;
|
ConditionalDataBinding = conditionalDataBinding;
|
||||||
ConditionViewModels = new BindableCollection<DataBindingConditionViewModel<TLayerProperty, TProperty>>();
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
|
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
|
||||||
public BindableCollection<DataBindingConditionViewModel<TLayerProperty, TProperty>> ConditionViewModels { get; }
|
|
||||||
|
public void AddCondition()
|
||||||
|
{
|
||||||
|
DataBindingCondition<TLayerProperty, TProperty> condition = ConditionalDataBinding.AddCondition();
|
||||||
|
|
||||||
|
// Find the VM of the new condition
|
||||||
|
DataBindingConditionViewModel<TLayerProperty, TProperty> 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<DataBindingConditionViewModel<TLayerProperty, TProperty>> toRemove = Items.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList();
|
||||||
|
foreach (DataBindingConditionViewModel<TLayerProperty, TProperty> dataBindingConditionViewModel in toRemove)
|
||||||
|
{
|
||||||
|
Items.Remove(dataBindingConditionViewModel);
|
||||||
|
dataBindingConditionViewModel.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing VMs
|
||||||
|
foreach (DataBindingCondition<TLayerProperty, TProperty> condition in ConditionalDataBinding.Conditions)
|
||||||
|
if (Items.All(c => c.DataBindingCondition != condition))
|
||||||
|
Items.Add(_dataBindingsVmFactory.DataBindingConditionViewModel(condition));
|
||||||
|
|
||||||
|
// Fix order
|
||||||
|
((BindableCollection<DataBindingConditionViewModel<TLayerProperty, TProperty>>) 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<TLayerProperty, TProperty> 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 bool SupportsTestValue => false;
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
UpdateConditionViewModels();
|
UpdateItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object GetTestValue()
|
public object GetTestValue()
|
||||||
@ -49,64 +114,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
|
|||||||
{
|
{
|
||||||
ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated;
|
ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated;
|
||||||
|
|
||||||
foreach (DataBindingConditionViewModel<TLayerProperty, TProperty> conditionViewModel in ConditionViewModels)
|
foreach (DataBindingConditionViewModel<TLayerProperty, TProperty> conditionViewModel in Items)
|
||||||
conditionViewModel.Dispose();
|
conditionViewModel.Dispose();
|
||||||
ConditionViewModels.Clear();
|
Items.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void UpdateConditionViewModels()
|
|
||||||
{
|
|
||||||
_updating = true;
|
|
||||||
|
|
||||||
// Remove old VMs
|
|
||||||
List<DataBindingConditionViewModel<TLayerProperty, TProperty>> toRemove = ConditionViewModels.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList();
|
|
||||||
foreach (DataBindingConditionViewModel<TLayerProperty, TProperty> dataBindingConditionViewModel in toRemove)
|
|
||||||
{
|
|
||||||
ConditionViewModels.Remove(dataBindingConditionViewModel);
|
|
||||||
dataBindingConditionViewModel.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add missing VMs
|
|
||||||
foreach (DataBindingCondition<TLayerProperty, TProperty> 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<TLayerProperty, TProperty> conditionViewModel = ConditionViewModels[index];
|
|
||||||
conditionViewModel.DataBindingCondition.Order = index + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConditionalDataBinding.ApplyOrder();
|
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConditionalDataBindingOnConditionsUpdated(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
UpdateConditionViewModels();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@
|
|||||||
IsTabStop="False" />
|
IsTabStop="False" />
|
||||||
|
|
||||||
<ContentControl Grid.Row="1"
|
<ContentControl Grid.Row="1"
|
||||||
Margin="26 5 0 0"
|
Margin="26 2 0 0"
|
||||||
s:View.Model="{Binding ValueViewModel}"
|
s:View.Model="{Binding ValueViewModel}"
|
||||||
VerticalContentAlignment="Stretch"
|
VerticalContentAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
|
|||||||
@ -13,6 +13,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
|
|||||||
public class DataBindingConditionViewModel<TLayerProperty, TProperty> : Conductor<DataModelConditionGroupViewModel>, IDisposable
|
public class DataBindingConditionViewModel<TLayerProperty, TProperty> : Conductor<DataModelConditionGroupViewModel>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
|
||||||
|
private readonly IDataModelUIService _dataModelUIService;
|
||||||
|
|
||||||
public DataBindingConditionViewModel(DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition,
|
public DataBindingConditionViewModel(DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
@ -20,22 +22,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
|
|||||||
IDataModelUIService dataModelUIService)
|
IDataModelUIService dataModelUIService)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
|
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||||
|
_dataModelUIService = dataModelUIService;
|
||||||
DataBindingCondition = dataBindingCondition;
|
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<TLayerProperty, TProperty> DataBindingCondition { get; }
|
public DataBindingCondition<TLayerProperty, TProperty> DataBindingCondition { get; }
|
||||||
|
|
||||||
public DataModelStaticViewModel ValueViewModel { get; set; }
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ValueViewModel.Dispose();
|
ValueViewModel.Dispose();
|
||||||
|
|||||||
@ -44,7 +44,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
|||||||
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
|
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
|
||||||
TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
|
TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
|
||||||
TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
|
TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialActivate()
|
||||||
|
{
|
||||||
|
base.OnInitialActivate();
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +241,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
|||||||
}
|
}
|
||||||
|
|
||||||
// While playing in preview data bindings aren't updated
|
// While playing in preview data bindings aren't updated
|
||||||
Registration.DataBinding.Update(0.04);
|
Registration.DataBinding.UpdateWithDelta(TimeSpan.FromMilliseconds(40));
|
||||||
|
|
||||||
if (ActiveItem.SupportsTestValue)
|
if (ActiveItem.SupportsTestValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
|||||||
// and creating the actual data bindings
|
// and creating the actual data bindings
|
||||||
foreach (IDataBindingRegistration registration in registrations)
|
foreach (IDataBindingRegistration registration in registrations)
|
||||||
Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration));
|
Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration));
|
||||||
|
|
||||||
SelectedItemIndex = 0;
|
SelectedItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -140,7 +140,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
|
|||||||
if (DynamicSelectionViewModel != null)
|
if (DynamicSelectionViewModel != null)
|
||||||
DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
|
DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
|
||||||
else if (StaticInputViewModel != null)
|
else if (StaticInputViewModel != null)
|
||||||
|
{
|
||||||
|
// Ensure the right static value is never null when the preferred type is a value type
|
||||||
StaticInputViewModel.Value = Modifier.ParameterStaticValue;
|
StaticInputViewModel.Value = Modifier.ParameterStaticValue;
|
||||||
|
if (SelectedModifierType.ParameterType.IsValueType && StaticInputViewModel.Value == null)
|
||||||
|
StaticInputViewModel.Value = SelectedModifierType.ParameterType.GetDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteSelectModifierTypeCommand(object context)
|
private void ExecuteSelectModifierTypeCommand(object context)
|
||||||
|
|||||||
@ -567,7 +567,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
|||||||
{
|
{
|
||||||
if (Repeating && RepeatTimeline)
|
if (Repeating && RepeatTimeline)
|
||||||
{
|
{
|
||||||
if (newTime > SelectedProfileElement.TimelineLength)
|
if (newTime > SelectedProfileElement.Timeline.Length)
|
||||||
newTime = TimeSpan.Zero;
|
newTime = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
else if (Repeating && RepeatSegment)
|
else if (Repeating && RepeatSegment)
|
||||||
@ -575,9 +575,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
|||||||
if (newTime > GetCurrentSegmentEnd())
|
if (newTime > GetCurrentSegmentEnd())
|
||||||
newTime = GetCurrentSegmentStart();
|
newTime = GetCurrentSegmentStart();
|
||||||
}
|
}
|
||||||
else if (newTime > SelectedProfileElement.TimelineLength)
|
else if (newTime > SelectedProfileElement.Timeline.Length)
|
||||||
{
|
{
|
||||||
newTime = SelectedProfileElement.TimelineLength;
|
newTime = SelectedProfileElement.Timeline.Length;
|
||||||
Pause();
|
Pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user