mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'feature/visual-scripting' into feature/avalonia
This commit is contained in:
commit
4ef7c83c74
@ -84,4 +84,8 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="DefaultTypes\DataBindings\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<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/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cnodes/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofileconfiguration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofileconfiguration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
|
||||||
@ -43,12 +44,15 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers_005Cabstract/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers_005Cabstract/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel_005Cvaluechangedevent/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel_005Cvaluechangedevent/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdisplay/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Cattributes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Cattributes/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayershapes/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayershapes/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface_005Clayout/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface_005Clayout/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cvisualscripting/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cvisualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mvvm/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mvvm/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=placeholders/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=placeholders/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
|
||||||
@ -83,4 +87,6 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@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>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@ -77,13 +77,13 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||||
{
|
{
|
||||||
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
|
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
||||||
{
|
{
|
||||||
TypeNameHandling = TypeNameHandling.All,
|
TypeNameHandling = TypeNameHandling.All,
|
||||||
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
|
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class EnumContainsConditionOperator : ConditionOperator<Enum, Enum>
|
|
||||||
{
|
|
||||||
public override string Description => "Contains";
|
|
||||||
public override string Icon => "Contain";
|
|
||||||
|
|
||||||
public override bool Evaluate(Enum a, Enum b)
|
|
||||||
{
|
|
||||||
return a != null && b != null && a.HasFlag(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class EnumNotContainsConditionOperator : ConditionOperator<Enum, Enum>
|
|
||||||
{
|
|
||||||
public override string Description => "Does not contain";
|
|
||||||
public override string Icon => "FormatStrikethrough";
|
|
||||||
|
|
||||||
public override bool Evaluate(Enum a, Enum b)
|
|
||||||
{
|
|
||||||
return a != null && (b == null || !a.HasFlag(b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class EqualsConditionOperator : ConditionOperator<object, object>
|
|
||||||
{
|
|
||||||
public override string Description => "Equals";
|
|
||||||
public override string Icon => "Equal";
|
|
||||||
|
|
||||||
public override bool Evaluate(object a, object b)
|
|
||||||
{
|
|
||||||
return Equals(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class GreaterThanConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Is greater than";
|
|
||||||
public override string Icon => "GreaterThan";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
return a > b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class GreaterThanOrEqualConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Is greater than or equal to";
|
|
||||||
public override string Icon => "GreaterThanOrEqual";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
return a >= b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class LessThanConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Is less than";
|
|
||||||
public override string Icon => "LessThan";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
return a < b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class LessThanOrEqualConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Is less than or equal to";
|
|
||||||
public override string Icon => "LessThanOrEqual";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
return a <= b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class NotEqualConditionOperator : ConditionOperator<object, object>
|
|
||||||
{
|
|
||||||
public override string Description => "Does not equal";
|
|
||||||
public override string Icon => "NotEqualVariant";
|
|
||||||
|
|
||||||
public override bool Evaluate(object a, object b)
|
|
||||||
{
|
|
||||||
return !Equals(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class NotNullConditionOperator : ConditionOperator<object>
|
|
||||||
{
|
|
||||||
public override string Description => "Is not null";
|
|
||||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
|
||||||
|
|
||||||
public override bool Evaluate(object a)
|
|
||||||
{
|
|
||||||
return a != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class NullConditionOperator : ConditionOperator<object>
|
|
||||||
{
|
|
||||||
public override string Description => "Is null";
|
|
||||||
public override string Icon => "Null";
|
|
||||||
|
|
||||||
public override bool Evaluate(object a)
|
|
||||||
{
|
|
||||||
return a == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class NumberEqualsConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Equals";
|
|
||||||
public override string Icon => "Equal";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
// Numbers can be tricky, an epsilon like this is close enough
|
|
||||||
return Math.Abs(a - b) < 0.000001;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class NumberNotEqualConditionOperator : ConditionOperator<double, double>
|
|
||||||
{
|
|
||||||
public override string Description => "Does not equal";
|
|
||||||
public override string Icon => "NotEqualVariant";
|
|
||||||
|
|
||||||
public override bool Evaluate(double a, double b)
|
|
||||||
{
|
|
||||||
// Numbers can be tricky, an epsilon like this is close enough
|
|
||||||
return Math.Abs(a - b) > 0.000001;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringContainsConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Contains";
|
|
||||||
public override string Icon => "Contain";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return a != null && b != null && a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringEndsWithConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Ends with";
|
|
||||||
public override string Icon => "ContainEnd";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return a != null && b != null && a.EndsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringEqualsConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Equals";
|
|
||||||
public override string Icon => "Equal";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Artemis.Core {
|
|
||||||
internal class StringMatchesRegexConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Matches Regex";
|
|
||||||
public override string Icon => "Regex";
|
|
||||||
|
|
||||||
public override bool Evaluate(string text, string regex)
|
|
||||||
{
|
|
||||||
// Ensures full match
|
|
||||||
if (!regex.StartsWith("^"))
|
|
||||||
regex = "^" + regex;
|
|
||||||
if (!regex.EndsWith("$"))
|
|
||||||
regex += "$";
|
|
||||||
|
|
||||||
return Regex.IsMatch(text, regex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringNotContainsConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Does not contain";
|
|
||||||
public override string Icon => "FormatStrikethrough";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringNotEqualConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Does not equal";
|
|
||||||
public override string Icon => "NotEqualVariant";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return !string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringNotNullConditionOperator : ConditionOperator<string>
|
|
||||||
{
|
|
||||||
public override string Description => "Is not null";
|
|
||||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a)
|
|
||||||
{
|
|
||||||
return !string.IsNullOrWhiteSpace(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringNullConditionOperator : ConditionOperator<string>
|
|
||||||
{
|
|
||||||
public override string Description => "Is null";
|
|
||||||
public override string Icon => "Null";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a)
|
|
||||||
{
|
|
||||||
return string.IsNullOrWhiteSpace(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class StringStartsWithConditionOperator : ConditionOperator<string, string>
|
|
||||||
{
|
|
||||||
public override string Description => "Starts with";
|
|
||||||
public override string Icon => "ContainStart";
|
|
||||||
|
|
||||||
public override bool Evaluate(string a, string b)
|
|
||||||
{
|
|
||||||
return a != null && b != null && a.StartsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class FloatDataBindingConverter : FloatDataBindingConverter<float>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <typeparam name="T">The type of layer property this converter is applied to</typeparam>
|
|
||||||
public class FloatDataBindingConverter<T> : DataBindingConverter<T, float>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="FloatDataBindingConverter{T}" /> class
|
|
||||||
/// </summary>
|
|
||||||
public FloatDataBindingConverter()
|
|
||||||
{
|
|
||||||
SupportsSum = true;
|
|
||||||
SupportsInterpolate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override float Sum(float a, float b)
|
|
||||||
{
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override float Interpolate(float a, float b, double progress)
|
|
||||||
{
|
|
||||||
float diff = b - a;
|
|
||||||
return (float) (a + diff * progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void ApplyValue(float value)
|
|
||||||
{
|
|
||||||
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
|
|
||||||
value = Math.Min(value, max);
|
|
||||||
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
|
|
||||||
value = Math.Max(value, min);
|
|
||||||
|
|
||||||
base.ApplyValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a generic data binding converter that acts as the bridge between a
|
|
||||||
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" /> and does not support
|
|
||||||
/// sum or interpolation
|
|
||||||
/// </summary>
|
|
||||||
public class GeneralDataBindingConverter<T> : DataBindingConverter<T, T>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="GeneralDataBindingConverter{T}" /> class
|
|
||||||
/// </summary>
|
|
||||||
public GeneralDataBindingConverter()
|
|
||||||
{
|
|
||||||
SupportsSum = false;
|
|
||||||
SupportsInterpolate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override T Sum(T a, T b)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override T Interpolate(T a, T b, double progress)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class IntDataBindingConverter : IntDataBindingConverter<int>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class IntDataBindingConverter<T> : DataBindingConverter<T, int>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="IntDataBindingConverter{T}" /> class
|
|
||||||
/// </summary>
|
|
||||||
public IntDataBindingConverter()
|
|
||||||
{
|
|
||||||
SupportsSum = true;
|
|
||||||
SupportsInterpolate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="MidpointRounding" /> mode used for rounding during interpolation. Defaults to
|
|
||||||
/// <see cref="MidpointRounding.AwayFromZero" />
|
|
||||||
/// </summary>
|
|
||||||
public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int Sum(int a, int b)
|
|
||||||
{
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int Interpolate(int a, int b, double progress)
|
|
||||||
{
|
|
||||||
int diff = b - a;
|
|
||||||
return (int) Math.Round(a + diff * progress, InterpolationRoundingMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class SKColorDataBindingConverter : DataBindingConverter<SKColor, SKColor>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="SKColorDataBindingConverter" /> class
|
|
||||||
/// </summary>
|
|
||||||
public SKColorDataBindingConverter()
|
|
||||||
{
|
|
||||||
SupportsInterpolate = true;
|
|
||||||
SupportsSum = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override SKColor Sum(SKColor a, SKColor b)
|
|
||||||
{
|
|
||||||
return a.Sum(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
|
|
||||||
{
|
|
||||||
return a.Interpolate(b, (float) progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SKColorBrightenModifierType : DataBindingModifierType<SKColor, float>
|
|
||||||
{
|
|
||||||
public override string Name => "Brighten by";
|
|
||||||
public override string Icon => "CarLightHigh";
|
|
||||||
public override string Description => "Brightens the color by the amount in percent";
|
|
||||||
|
|
||||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
|
||||||
{
|
|
||||||
currentValue.ToHsl(out float h, out float s, out float l);
|
|
||||||
l *= (parameterValue + 100f) / 100f;
|
|
||||||
return SKColor.FromHsl(h, s, l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SKColorDarkenModifierType : DataBindingModifierType<SKColor, float>
|
|
||||||
{
|
|
||||||
public override string Name => "Darken by";
|
|
||||||
public override string Icon => "CarLightDimmed";
|
|
||||||
public override string Description => "Darkens the color by the amount in percent";
|
|
||||||
|
|
||||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
|
||||||
{
|
|
||||||
currentValue.ToHsl(out float h, out float s, out float l);
|
|
||||||
l *= (parameterValue * -1 + 100f) / 100f;
|
|
||||||
return SKColor.FromHsl(h, s, l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
currentValue.ToHsl(out float h, out float s, out float l);
|
|
||||||
s -= parameterValue;
|
|
||||||
s = Math.Clamp(s, 0, 100);
|
|
||||||
|
|
||||||
return SKColor.FromHsl(h, s, l, currentValue.Alpha);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SKColorInvertModifierType : DataBindingModifierType<SKColor>
|
|
||||||
{
|
|
||||||
public override string Name => "Invert color";
|
|
||||||
public override string Icon => "InvertColors";
|
|
||||||
public override string Description => "Inverts the color by rotating its hue by a 180°";
|
|
||||||
|
|
||||||
public override SKColor Apply(SKColor currentValue)
|
|
||||||
{
|
|
||||||
currentValue.ToHsl(out float h, out float s, out float l);
|
|
||||||
h += 180;
|
|
||||||
return SKColor.FromHsl(h % 360, s, l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SKColorRotateHueModifierType : DataBindingModifierType<SKColor, float>
|
|
||||||
{
|
|
||||||
public override string Name => "Rotate Hue by";
|
|
||||||
public override string Icon => "RotateRight";
|
|
||||||
public override string Description => "Rotates the hue of the color by the amount in degrees";
|
|
||||||
|
|
||||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
|
||||||
{
|
|
||||||
currentValue.ToHsl(out float h, out float s, out float l);
|
|
||||||
h += parameterValue;
|
|
||||||
return SKColor.FromHsl(h % 360, s, l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
currentValue.ToHsv(out float h, out float s, out float v);
|
|
||||||
s += parameterValue;
|
|
||||||
s = Math.Clamp(s, 0, 100);
|
|
||||||
|
|
||||||
return SKColor.FromHsv(h, s, v, currentValue.Alpha);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SKColorSumModifierType : DataBindingModifierType<SKColor, SKColor>
|
|
||||||
{
|
|
||||||
public override string Name => "Combine with";
|
|
||||||
public override string Icon => "FormatColorFill";
|
|
||||||
public override string Description => "Adds the two colors together";
|
|
||||||
|
|
||||||
public override SKColor Apply(SKColor currentValue, SKColor parameterValue)
|
|
||||||
{
|
|
||||||
return currentValue.Sum(parameterValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class AbsoluteModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Absolute";
|
|
||||||
public override string Icon => "NumericPositive1";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Converts the input value to an absolute value";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Abs(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class DivideModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Divide by";
|
|
||||||
public override string Icon => "Divide";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
// Ye ye none of that
|
|
||||||
if (parameterValue == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return currentValue / parameterValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class MaxModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Max";
|
|
||||||
public override string Icon => "ChevronUpBoxOutline";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Keeps only the largest of input value and parameter";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return Math.Max(currentValue, parameterValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class MinModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Min";
|
|
||||||
public override string Icon => "ChevronDownBoxOutline";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Keeps only the smallest of input value and parameter";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return Math.Min(currentValue, parameterValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class ModuloModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Modulo";
|
|
||||||
public override string Icon => "Stairs";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Calculates the remained of the division between the input value and the parameter";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return currentValue % parameterValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class MultiplicationModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Multiply by";
|
|
||||||
public override string Icon => "Close";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return currentValue * parameterValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class PercentageOfModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Percentage of";
|
|
||||||
public override string Icon => "Percent";
|
|
||||||
public override string Description => "Calculates how much percent the parameter value is of the current value";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
// Ye ye none of that
|
|
||||||
if (parameterValue == 0d)
|
|
||||||
return 100d;
|
|
||||||
|
|
||||||
return 100d / parameterValue * currentValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class PowerModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Power";
|
|
||||||
public override string Icon => "Exponent";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Raises the input value to the power of the parameter value";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return Math.Pow(currentValue, parameterValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class CeilingModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Round up";
|
|
||||||
public override string Icon => "ArrowUp";
|
|
||||||
public override string Category => "Rounding";
|
|
||||||
public override string Description => "Ceils the input, rounding it up to the nearest whole number";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Ceiling(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class FloorModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Round down";
|
|
||||||
public override string Icon => "ArrowDown";
|
|
||||||
public override string Category => "Rounding";
|
|
||||||
public override string Description => "Floors the input, rounding it down to the nearest whole number";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Floor(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class RoundModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Round";
|
|
||||||
public override string Icon => "ArrowCollapse";
|
|
||||||
public override string Category => "Rounding";
|
|
||||||
public override string Description => "Rounds the input to the nearest whole number";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Round(currentValue, MidpointRounding.AwayFromZero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SquareRootModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Square root";
|
|
||||||
public override string Icon => "SquareRoot";
|
|
||||||
public override string Category => "Advanced";
|
|
||||||
public override string Description => "Calculates square root of the input value";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Sqrt(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SubtractModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Subtract";
|
|
||||||
public override string Icon => "Minus";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return currentValue - parameterValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SumModifierType : DataBindingModifierType<double, double>
|
|
||||||
{
|
|
||||||
public override string Name => "Sum";
|
|
||||||
public override string Icon => "Plus";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue, double parameterValue)
|
|
||||||
{
|
|
||||||
return currentValue + parameterValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class CosecantModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Cosecant";
|
|
||||||
public override string? Icon => null;
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the cosecant";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return 1d / Math.Sin(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class CosineModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Cosine";
|
|
||||||
public override string Icon => "MathCos";
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the cosine";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Cos(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class CotangentModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Cotangent";
|
|
||||||
public override string? Icon => null;
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the cotangent";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return 1d / Math.Tan(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SecantModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Secant";
|
|
||||||
public override string? Icon => null;
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the secant";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return 1d / Math.Cos(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class SineModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Sine";
|
|
||||||
public override string Icon => "MathSin";
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the sine";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Sin(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class TangentModifierType : DataBindingModifierType<double>
|
|
||||||
{
|
|
||||||
public override string Name => "Tangent";
|
|
||||||
public override string Icon => "MathTan";
|
|
||||||
public override string Category => "Trigonometry";
|
|
||||||
public override string Description => "Treats the input as an angle and calculates the tangent";
|
|
||||||
|
|
||||||
public override double Apply(double currentValue)
|
|
||||||
{
|
|
||||||
return Math.Tan(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,9 +4,14 @@
|
|||||||
public class BoolLayerProperty : LayerProperty<bool>
|
public class BoolLayerProperty : LayerProperty<bool>
|
||||||
{
|
{
|
||||||
internal BoolLayerProperty()
|
internal BoolLayerProperty()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
{
|
{
|
||||||
KeyframesSupported = false;
|
KeyframesSupported = false;
|
||||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ namespace Artemis.Core
|
|||||||
internal ColorGradientLayerProperty()
|
internal ColorGradientLayerProperty()
|
||||||
{
|
{
|
||||||
KeyframesSupported = false;
|
KeyframesSupported = false;
|
||||||
DataBindingsSupported = true;
|
|
||||||
DefaultValue = new ColorGradient();
|
DefaultValue = new ColorGradient();
|
||||||
|
|
||||||
CurrentValueSet += OnCurrentValueSet;
|
CurrentValueSet += OnCurrentValueSet;
|
||||||
@ -20,7 +19,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
private void CreateDataBindingRegistrations()
|
private void CreateDataBindingRegistrations()
|
||||||
{
|
{
|
||||||
ClearDataBindingProperties();
|
DataBinding.ClearDataBindingProperties();
|
||||||
if (CurrentValue == null)
|
if (CurrentValue == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ namespace Artemis.Core
|
|||||||
CurrentValue[stopIndex].Color = value;
|
CurrentValue[stopIndex].Color = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, $"Color #{stopIndex + 1}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
|
if (CurrentValue.Count != DataBinding.Properties.Count)
|
||||||
CreateDataBindingRegistrations();
|
CreateDataBindingRegistrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,25 +88,4 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ColorStopDataBindingConverter : DataBindingConverter<ColorGradient, SKColor>
|
|
||||||
{
|
|
||||||
public ColorStopDataBindingConverter()
|
|
||||||
{
|
|
||||||
SupportsInterpolate = true;
|
|
||||||
SupportsSum = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override SKColor Sum(SKColor a, SKColor b)
|
|
||||||
{
|
|
||||||
return a.Sum(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
|
|
||||||
{
|
|
||||||
return a.Interpolate(b, (float) progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -8,7 +8,6 @@ namespace Artemis.Core
|
|||||||
internal EnumLayerProperty()
|
internal EnumLayerProperty()
|
||||||
{
|
{
|
||||||
KeyframesSupported = false;
|
KeyframesSupported = false;
|
||||||
DataBindingsSupported = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -5,7 +5,12 @@
|
|||||||
{
|
{
|
||||||
internal FloatLayerProperty()
|
internal FloatLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -5,13 +5,17 @@
|
|||||||
{
|
{
|
||||||
internal FloatRangeLayerProperty()
|
internal FloatRangeLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
|
|
||||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
|
|
||||||
|
|
||||||
CurrentValueSet += OnCurrentValueSet;
|
CurrentValueSet += OnCurrentValueSet;
|
||||||
DefaultValue = new FloatRange();
|
DefaultValue = new FloatRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start");
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End");
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,7 +7,12 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal IntLayerProperty()
|
internal IntLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -5,13 +5,17 @@
|
|||||||
{
|
{
|
||||||
internal IntRangeLayerProperty()
|
internal IntRangeLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
|
|
||||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
|
|
||||||
|
|
||||||
CurrentValueSet += OnCurrentValueSet;
|
CurrentValueSet += OnCurrentValueSet;
|
||||||
DefaultValue = new IntRange();
|
DefaultValue = new IntRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start");
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End");
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
internal LayerBrushReferenceLayerProperty()
|
internal LayerBrushReferenceLayerProperty()
|
||||||
{
|
{
|
||||||
KeyframesSupported = false;
|
KeyframesSupported = false;
|
||||||
DataBindingsSupported = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -7,7 +7,12 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal SKColorLayerProperty()
|
internal SKColorLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -7,8 +7,13 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal SKPointLayerProperty()
|
internal SKPointLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
|
}
|
||||||
RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X");
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -7,8 +7,13 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
internal SKSizeLayerProperty()
|
internal SKSizeLayerProperty()
|
||||||
{
|
{
|
||||||
RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
|
}
|
||||||
RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), "Width");
|
||||||
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), "Height");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
20
src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs
Normal file
20
src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data for data binding events.
|
||||||
|
/// </summary>
|
||||||
|
public class DataBindingEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
internal DataBindingEventArgs(IDataBinding dataBinding)
|
||||||
|
{
|
||||||
|
DataBinding = dataBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data binding this event is related to
|
||||||
|
/// </summary>
|
||||||
|
public IDataBinding DataBinding { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class ConditionOperatorStoreEvent
|
|
||||||
{
|
|
||||||
public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration)
|
|
||||||
{
|
|
||||||
Registration = registration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConditionOperatorRegistration Registration { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class DataBindingModifierTypeStoreEvent
|
|
||||||
{
|
|
||||||
public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration)
|
|
||||||
{
|
|
||||||
TypeRegistration = typeRegistration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataBindingModifierTypeRegistration TypeRegistration { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs
Normal file
12
src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
internal class NodeTypeStoreEvent
|
||||||
|
{
|
||||||
|
public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration)
|
||||||
|
{
|
||||||
|
TypeRegistration = typeRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeTypeRegistration TypeRegistration { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -92,5 +92,25 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the index of the provided element inside the read only collection
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of element to find</typeparam>
|
||||||
|
/// <param name="self">The collection to search in</param>
|
||||||
|
/// <param name="elementToFind">The element to find</param>
|
||||||
|
/// <returns>If found, the index of the element to find; otherwise -1</returns>
|
||||||
|
public static int IndexOf<T>(this IReadOnlyCollection<T> self, T elementToFind)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
foreach (T element in self)
|
||||||
|
{
|
||||||
|
if (Equals(element, elementToFind))
|
||||||
|
return i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,6 +57,19 @@ namespace Artemis.Core
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Darkens the color by the specified amount
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">The color to darken</param>
|
||||||
|
/// <param name="amount">The brightness of the new color</param>
|
||||||
|
/// <returns>The darkened color</returns>
|
||||||
|
public static SKColor Darken(this SKColor c, float amount)
|
||||||
|
{
|
||||||
|
c.ToHsl(out float h, out float s, out float l);
|
||||||
|
l *= 1f - amount;
|
||||||
|
return SKColor.FromHsl(h, s, l);
|
||||||
|
}
|
||||||
|
|
||||||
private static byte ClampToByte(float value)
|
private static byte ClampToByte(float value)
|
||||||
{
|
{
|
||||||
return (byte) Math.Clamp(value, 0, 255);
|
return (byte) Math.Clamp(value, 0, 255);
|
||||||
|
|||||||
@ -130,8 +130,11 @@ namespace Artemis.Core
|
|||||||
/// at all
|
/// at all
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static int ScoreCastability(this Type to, Type from)
|
public static int ScoreCastability(this Type to, Type? from)
|
||||||
{
|
{
|
||||||
|
if (from == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (to == from)
|
if (to == from)
|
||||||
return 5;
|
return 5;
|
||||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||||
|
|||||||
25
src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
Normal file
25
src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Artemis.Core.JsonConverters
|
||||||
|
{
|
||||||
|
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||||
|
{
|
||||||
|
#region Overrides of JsonConverter<Numeric>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
float floatValue = value;
|
||||||
|
writer.WriteValue(floatValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
return new Numeric(reader.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,82 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a condition operator that performs a boolean operation
|
|
||||||
/// <para>
|
|
||||||
/// To implement your own condition operator, inherit <see cref="ConditionOperator{TLeftSide, TRightSide}" /> or
|
|
||||||
/// <see cref="ConditionOperator{TLeftSide}" />
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BaseConditionOperator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the description of this logical operator
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Description { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the icon of this logical operator
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Icon { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the plugin this condition operator belongs to
|
|
||||||
/// <para>Note: Not set until after registering</para>
|
|
||||||
/// </summary>
|
|
||||||
public Plugin? Plugin { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the left side type of this condition operator
|
|
||||||
/// </summary>
|
|
||||||
public abstract Type LeftSideType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the right side type of this condition operator. May be null if the operator does not support a right side
|
|
||||||
/// </summary>
|
|
||||||
public abstract Type? RightSideType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether the given type is supported by the operator
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
|
|
||||||
/// <param name="side">Which side of the operator to check, left or right</param>
|
|
||||||
public bool SupportsType(Type type, ConditionParameterSide side)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
return true;
|
|
||||||
if (side == ConditionParameterSide.Left)
|
|
||||||
return LeftSideType.IsCastableFrom(type);
|
|
||||||
return RightSideType != null && RightSideType.IsCastableFrom(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the condition with the input types being provided as objects
|
|
||||||
/// <para>
|
|
||||||
/// This leaves the caller responsible for the types matching <see cref="LeftSideType" /> and
|
|
||||||
/// <see cref="RightSideType" />
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="leftSideValue">The left side value, type should match <see cref="LeftSideType" /></param>
|
|
||||||
/// <param name="rightSideValue">The right side value, type should match <see cref="RightSideType" /></param>
|
|
||||||
/// <returns>The result of the boolean condition's evaluation</returns>
|
|
||||||
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a side of a condition parameter
|
|
||||||
/// </summary>
|
|
||||||
public enum ConditionParameterSide
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The left side of a condition parameter
|
|
||||||
/// </summary>
|
|
||||||
Left,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The right side of a condition parameter
|
|
||||||
/// </summary>
|
|
||||||
Right
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a condition operator that performs a boolean operation using a left- and right-side
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type LeftSideType => typeof(TLeftSide);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type RightSideType => typeof(TRightSide);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the operator on a and b
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="a">The parameter on the left side of the expression</param>
|
|
||||||
/// <param name="b">The parameter on the right side of the expression</param>
|
|
||||||
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
|
||||||
{
|
|
||||||
// TODO: Can we avoid boxing/unboxing?
|
|
||||||
TLeftSide leftSide;
|
|
||||||
if (leftSideValue != null)
|
|
||||||
{
|
|
||||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
|
||||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
|
||||||
else
|
|
||||||
leftSide = (TLeftSide) leftSideValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftSide = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
TRightSide rightSide;
|
|
||||||
if (rightSideValue != null)
|
|
||||||
{
|
|
||||||
if (rightSideValue.GetType() != typeof(TRightSide) && leftSideValue is IConvertible)
|
|
||||||
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
|
|
||||||
else
|
|
||||||
rightSide = (TRightSide) rightSideValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rightSide = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Evaluate(leftSide!, rightSide!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a condition operator that performs a boolean operation using only a left side
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type LeftSideType => typeof(TLeftSide);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Always <c>null</c>, not applicable to this type of condition operator
|
|
||||||
/// </summary>
|
|
||||||
public override Type? RightSideType => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the operator on a and b
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="a">The parameter on the left side of the expression</param>
|
|
||||||
public abstract bool Evaluate(TLeftSide a);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
|
||||||
{
|
|
||||||
// TODO: Can we avoid boxing/unboxing?
|
|
||||||
TLeftSide leftSide;
|
|
||||||
if (leftSideValue != null)
|
|
||||||
{
|
|
||||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
|
||||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
|
||||||
else
|
|
||||||
leftSide = (TLeftSide) leftSideValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftSide = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Evaluate(leftSide!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An abstract class for display condition parts
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DataModelConditionPart : IDisposable
|
|
||||||
{
|
|
||||||
private readonly List<DataModelConditionPart> _children = new();
|
|
||||||
|
|
||||||
protected DataModelConditionPart()
|
|
||||||
{
|
|
||||||
Children = new ReadOnlyCollection<DataModelConditionPart>(_children);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parent of this part
|
|
||||||
/// </summary>
|
|
||||||
public DataModelConditionPart? Parent { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the children of this part
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyCollection<DataModelConditionPart> Children { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a child to the display condition part's <see cref="Children" /> collection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataModelConditionPart"></param>
|
|
||||||
/// <param name="index">An optional index at which to insert the condition</param>
|
|
||||||
public void AddChild(DataModelConditionPart dataModelConditionPart, int? index = null)
|
|
||||||
{
|
|
||||||
if (!_children.Contains(dataModelConditionPart))
|
|
||||||
{
|
|
||||||
dataModelConditionPart.Parent = this;
|
|
||||||
if (index != null)
|
|
||||||
_children.Insert(index.Value, dataModelConditionPart);
|
|
||||||
else
|
|
||||||
_children.Add(dataModelConditionPart);
|
|
||||||
|
|
||||||
OnChildAdded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a child from the display condition part's <see cref="Children" /> collection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataModelConditionPart">The child to remove</param>
|
|
||||||
public void RemoveChild(DataModelConditionPart dataModelConditionPart)
|
|
||||||
{
|
|
||||||
if (_children.Contains(dataModelConditionPart))
|
|
||||||
{
|
|
||||||
dataModelConditionPart.Parent = null;
|
|
||||||
_children.Remove(dataModelConditionPart);
|
|
||||||
OnChildRemoved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes all children. You monster.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearChildren()
|
|
||||||
{
|
|
||||||
while (Children.Any())
|
|
||||||
RemoveChild(Children[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the condition part on the data model
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public abstract bool Evaluate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the condition part on the given target (currently only for lists)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal abstract bool EvaluateObject(object? target);
|
|
||||||
|
|
||||||
internal abstract void Save();
|
|
||||||
internal abstract DataModelConditionPartEntity GetEntity();
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposed the condition part
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a child-condition was added
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? ChildAdded;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a child-condition was removed
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? ChildRemoved;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokers the <see cref="ChildAdded" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnChildAdded()
|
|
||||||
{
|
|
||||||
ChildAdded?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokers the <see cref="ChildRemoved" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnChildRemoved()
|
|
||||||
{
|
|
||||||
ChildRemoved?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,411 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
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 abstract class DataModelConditionPredicate : DataModelConditionPart
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent"></param>
|
|
||||||
/// <param name="predicateType"></param>
|
|
||||||
/// <param name="entity">A new empty entity</param>
|
|
||||||
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = entity;
|
|
||||||
PredicateType = predicateType;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = entity;
|
|
||||||
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the predicate type
|
|
||||||
/// </summary>
|
|
||||||
public ProfileRightSideType PredicateType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the operator
|
|
||||||
/// </summary>
|
|
||||||
public BaseConditionOperator? Operator { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the left property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? LeftPath { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the right property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? RightPath { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
|
||||||
/// <see cref="ProfileRightSideType.Static" />
|
|
||||||
/// </summary>
|
|
||||||
public object? RightStaticValue { get; protected set; }
|
|
||||||
|
|
||||||
internal DataModelConditionPredicateEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
|
||||||
return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}";
|
|
||||||
return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}";
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
|
||||||
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
|
||||||
|
|
||||||
LeftPath?.Dispose();
|
|
||||||
RightPath?.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Initialization
|
|
||||||
|
|
||||||
internal void Initialize()
|
|
||||||
{
|
|
||||||
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
|
|
||||||
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
|
|
||||||
|
|
||||||
InitializeLeftPath();
|
|
||||||
|
|
||||||
// Operator
|
|
||||||
if (Entity.OperatorPluginGuid != null)
|
|
||||||
{
|
|
||||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
|
||||||
if (conditionOperator != null)
|
|
||||||
UpdateOperator(conditionOperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right side dynamic
|
|
||||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
|
||||||
InitializeRightPath();
|
|
||||||
// Right side static
|
|
||||||
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// If the left path is not valid we cannot reliably set up the right side because the type is unknown
|
|
||||||
// Because of that wait for it to validate first
|
|
||||||
if (LeftPath != null && !LeftPath.IsValid)
|
|
||||||
{
|
|
||||||
LeftPath.PathValidated += InitializeRightSideStatic;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (LeftPath == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Use the left side type so JSON.NET has a better idea what to do
|
|
||||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
|
||||||
object? rightSideValue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rightSideValue = CoreJson.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);
|
|
||||||
}
|
|
||||||
catch (JsonReaderException e)
|
|
||||||
{
|
|
||||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeRightSideStatic(object? sender, EventArgs args)
|
|
||||||
{
|
|
||||||
if (LeftPath == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LeftPath.PathValidated -= InitializeRightSideStatic;
|
|
||||||
|
|
||||||
// Use the left side type so JSON.NET has a better idea what to do
|
|
||||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
|
||||||
object? rightSideValue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rightSideValue = CoreJson.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the left path of this condition predicate
|
|
||||||
/// </summary>
|
|
||||||
protected abstract void InitializeLeftPath();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the right path of this condition predicate
|
|
||||||
/// </summary>
|
|
||||||
protected abstract void InitializeRightPath();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Modification
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the left side of the predicate
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path pointing to the left side value inside the data model</param>
|
|
||||||
public virtual void UpdateLeftSide(DataModelPath? path)
|
|
||||||
{
|
|
||||||
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, makes the predicate dynamic and re-compiles the expression
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path pointing to the right side value inside the data model</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;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
Type? preferredType = GetPreferredRightSideType();
|
|
||||||
if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null)
|
|
||||||
RightStaticValue = staticValue;
|
|
||||||
else if (staticValue != null)
|
|
||||||
RightStaticValue = Convert.ChangeType(staticValue, preferredType);
|
|
||||||
// If null create a default instance for value types or simply make it null for reference types
|
|
||||||
else if (preferredType.IsValueType)
|
|
||||||
RightStaticValue = Activator.CreateInstance(preferredType);
|
|
||||||
else
|
|
||||||
RightStaticValue = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No need to check for compatibility without a left side, when left site does get set it will be checked again
|
|
||||||
if (LeftPath == null || !LeftPath.IsValid)
|
|
||||||
{
|
|
||||||
Operator = conditionOperator;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type leftType = LeftPath.GetPropertyType()!;
|
|
||||||
// Left side can't go empty so enforce a match
|
|
||||||
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
|
|
||||||
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
|
|
||||||
$"it does not support left side type {leftType.Name}");
|
|
||||||
|
|
||||||
Operator = conditionOperator;
|
|
||||||
ValidateRightSide();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the best type to use for the right side op this predicate
|
|
||||||
/// </summary>
|
|
||||||
public abstract Type? GetPreferredRightSideType();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#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 = CoreJson.SerializeObject(RightStaticValue);
|
|
||||||
|
|
||||||
if (Operator?.Plugin != null)
|
|
||||||
{
|
|
||||||
Entity.OperatorPluginGuid = Operator.Plugin.Guid;
|
|
||||||
Entity.OperatorType = Operator.GetType().Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
|
|
||||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
|
||||||
{
|
|
||||||
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
|
||||||
if (Entity.OperatorPluginGuid == conditionOperator.Plugin!.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
|
|
||||||
UpdateOperator(conditionOperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
|
|
||||||
{
|
|
||||||
if (e.Registration.ConditionOperator != Operator)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Operator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,257 +0,0 @@
|
|||||||
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? _valueChangedEvent;
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last time the event this condition is applied to was triggered
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LastTrigger { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the type of argument the event provides
|
|
||||||
/// </summary>
|
|
||||||
public Type? EventArgumentType { get; private set; }
|
|
||||||
|
|
||||||
internal DataModelConditionEventEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Evaluate()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
|
||||||
|
|
||||||
IDataModelEvent? dataModelEvent = GetDataModelEvent();
|
|
||||||
if (dataModelEvent == null)
|
|
||||||
return false;
|
|
||||||
dataModelEvent.Update();
|
|
||||||
|
|
||||||
// Only evaluate to true once every time the event has been triggered since the last evaluation
|
|
||||||
if (dataModelEvent.LastTrigger <= LastTrigger)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LastTrigger = DateTime.Now;
|
|
||||||
|
|
||||||
// If there is a child (root group), it must evaluate to true whenever the event triggered
|
|
||||||
if (Children.Any())
|
|
||||||
return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped);
|
|
||||||
|
|
||||||
// If there are no children, we always evaluate to true whenever the event triggered
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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();
|
|
||||||
CreateValueChangedEventIfNeeded();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by</returns>
|
|
||||||
public IDataModelEvent? GetDataModelEvent()
|
|
||||||
{
|
|
||||||
if (_valueChangedEvent != null)
|
|
||||||
return _valueChangedEvent;
|
|
||||||
return EventPath?.GetValue() as IDataModelEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
|
|
||||||
DataModelPath eventPath = new(null, Entity.EventPath);
|
|
||||||
EventPath = eventPath;
|
|
||||||
SubscribeToEventPath();
|
|
||||||
CreateValueChangedEventIfNeeded();
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type? GetEventArgumentType()
|
|
||||||
{
|
|
||||||
if (EventPath == null || !EventPath.IsValid)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (_valueChangedEvent != null)
|
|
||||||
return _valueChangedEvent.ArgumentsType;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SubscribeToEventPath()
|
|
||||||
{
|
|
||||||
if (EventPath == null) return;
|
|
||||||
EventPath.PathValidated += EventPathOnPathValidated;
|
|
||||||
EventPath.PathInvalidated += EventPathOnPathInvalidated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateValueChangedEventIfNeeded()
|
|
||||||
{
|
|
||||||
Type? propertyType = EventPath?.GetPropertyType();
|
|
||||||
if (propertyType == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!typeof(IDataModelEvent).IsAssignableFrom(propertyType))
|
|
||||||
{
|
|
||||||
IDataModelEvent? instance = (IDataModelEvent?) Activator.CreateInstance(typeof(DataModelValueChangedEvent<>).MakeGenericType(propertyType), EventPath);
|
|
||||||
_valueChangedEvent = instance ?? throw new ArtemisCoreException("Failed to create a DataModelValueChangedEvent for a property changed data model event");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_valueChangedEvent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void InitializeLeftPath()
|
|
||||||
{
|
|
||||||
if (Entity.LeftPath != null)
|
|
||||||
LeftPath = DataModelConditionEvent.EventArgumentType != null
|
|
||||||
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void InitializeLeftPath()
|
|
||||||
{
|
|
||||||
if (Entity.LeftPath != null)
|
|
||||||
LeftPath = new DataModelPath(null, Entity.LeftPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void InitializeRightPath()
|
|
||||||
{
|
|
||||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
|
||||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,190 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A group containing zero to many <see cref="DataModelConditionPart" />s which it evaluates using a boolean specific
|
|
||||||
/// operator
|
|
||||||
/// </summary>
|
|
||||||
public class DataModelConditionGroup : DataModelConditionPart
|
|
||||||
{
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent"></param>
|
|
||||||
public DataModelConditionGroup(DataModelConditionPart? parent)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = new DataModelConditionGroupEntity();
|
|
||||||
ChildAdded += OnChildrenChanged;
|
|
||||||
ChildRemoved += OnChildrenChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent"></param>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
public DataModelConditionGroup(DataModelConditionPart? parent, DataModelConditionGroupEntity entity)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = entity;
|
|
||||||
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
|
|
||||||
|
|
||||||
foreach (DataModelConditionPartEntity childEntity in Entity.Children)
|
|
||||||
{
|
|
||||||
if (childEntity is DataModelConditionGroupEntity groupEntity)
|
|
||||||
AddChild(new DataModelConditionGroup(this, groupEntity));
|
|
||||||
else if (childEntity is DataModelConditionListEntity listEntity)
|
|
||||||
AddChild(new DataModelConditionList(this, listEntity));
|
|
||||||
else if (childEntity is DataModelConditionEventEntity eventEntity)
|
|
||||||
AddChild(new DataModelConditionEvent(this, eventEntity));
|
|
||||||
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
|
|
||||||
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
|
|
||||||
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
|
|
||||||
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
|
|
||||||
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
|
|
||||||
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
|
||||||
ChildAdded += OnChildrenChanged;
|
|
||||||
ChildRemoved += OnChildrenChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the boolean operator of this group
|
|
||||||
/// </summary>
|
|
||||||
public BooleanOperator BooleanOperator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether this group contains any events
|
|
||||||
/// </summary>
|
|
||||||
public bool ContainsEvents { get; private set; }
|
|
||||||
|
|
||||||
internal DataModelConditionGroupEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Evaluate()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
|
||||||
|
|
||||||
// Empty groups are always true
|
|
||||||
if (Children.Count == 0)
|
|
||||||
return true;
|
|
||||||
// Groups with only one child ignore the boolean operator
|
|
||||||
if (Children.Count == 1)
|
|
||||||
return Children[0].Evaluate();
|
|
||||||
|
|
||||||
if (ContainsEvents)
|
|
||||||
{
|
|
||||||
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
|
|
||||||
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
|
|
||||||
}
|
|
||||||
return EvaluateWithOperator(Children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
|
|
||||||
{
|
|
||||||
return BooleanOperator switch
|
|
||||||
{
|
|
||||||
BooleanOperator.And => targets.All(c => c.Evaluate()),
|
|
||||||
BooleanOperator.Or => targets.Any(c => c.Evaluate()),
|
|
||||||
BooleanOperator.AndNot => targets.All(c => !c.Evaluate()),
|
|
||||||
BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()),
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
foreach (DataModelConditionPart child in Children)
|
|
||||||
child.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
internal override bool EvaluateObject(object? target)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
|
||||||
|
|
||||||
// Empty groups are always true
|
|
||||||
if (Children.Count == 0)
|
|
||||||
return true;
|
|
||||||
// Groups with only one child ignore the boolean operator
|
|
||||||
if (Children.Count == 1)
|
|
||||||
return Children[0].EvaluateObject(target);
|
|
||||||
|
|
||||||
return BooleanOperator switch
|
|
||||||
{
|
|
||||||
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
|
|
||||||
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
|
|
||||||
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
|
|
||||||
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Save()
|
|
||||||
{
|
|
||||||
Entity.BooleanOperator = (int) BooleanOperator;
|
|
||||||
|
|
||||||
Entity.Children.Clear();
|
|
||||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
|
||||||
foreach (DataModelConditionPart child in Children)
|
|
||||||
child.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override DataModelConditionPartEntity GetEntity()
|
|
||||||
{
|
|
||||||
return Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChildrenChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a boolean operator
|
|
||||||
/// </summary>
|
|
||||||
public enum BooleanOperator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// All the conditions in the group should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
And,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Any of the conditions in the group should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
Or,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All the conditions in the group should evaluate to false
|
|
||||||
/// </summary>
|
|
||||||
AndNot,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Any of the conditions in the group should evaluate to false
|
|
||||||
/// </summary>
|
|
||||||
OrNot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,276 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A condition that evaluates one or more predicates inside a list
|
|
||||||
/// </summary>
|
|
||||||
public class DataModelConditionList : DataModelConditionPart
|
|
||||||
{
|
|
||||||
private bool _disposed;
|
|
||||||
private bool _reinitializing;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionList" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent"></param>
|
|
||||||
public DataModelConditionList(DataModelConditionPart parent)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = new DataModelConditionListEntity();
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelConditionList(DataModelConditionPart parent, DataModelConditionListEntity entity)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
Entity = entity;
|
|
||||||
ListOperator = (ListOperator) entity.ListOperator;
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list operator
|
|
||||||
/// </summary>
|
|
||||||
public ListOperator ListOperator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the list property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? ListPath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of the content of the list this predicate is evaluated on
|
|
||||||
/// </summary>
|
|
||||||
public Type? ListType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the list contains primitives
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPrimitiveList { get; set; }
|
|
||||||
|
|
||||||
internal DataModelConditionListEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Evaluate()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionList");
|
|
||||||
|
|
||||||
if (ListPath == null || !ListPath.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return EvaluateObject(ListPath.GetValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the list the predicate is evaluated on
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path pointing to the list inside the list</param>
|
|
||||||
public void UpdateList(DataModelPath? path)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionList");
|
|
||||||
|
|
||||||
if (path != null && !path.IsValid)
|
|
||||||
throw new ArtemisCoreException("Cannot update list to an invalid path");
|
|
||||||
|
|
||||||
ListPath?.Dispose();
|
|
||||||
ListPath = path != null ? new DataModelPath(path) : null;
|
|
||||||
SubscribeToListPath();
|
|
||||||
|
|
||||||
// Remove the old root group that was tied to the old data model
|
|
||||||
while (Children.Any())
|
|
||||||
RemoveChild(Children[0]);
|
|
||||||
|
|
||||||
if (ListPath != null)
|
|
||||||
{
|
|
||||||
Type listType = ListPath.GetPropertyType()!;
|
|
||||||
ListType = listType.GetGenericEnumerableType();
|
|
||||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
|
||||||
|
|
||||||
// Create a new root group
|
|
||||||
AddChild(new DataModelConditionGroup(this));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ListType = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
ListPath?.Dispose();
|
|
||||||
|
|
||||||
foreach (DataModelConditionPart child in Children)
|
|
||||||
child.Dispose();
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal override bool EvaluateObject(object? target)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataModelConditionList");
|
|
||||||
|
|
||||||
if (!Children.Any())
|
|
||||||
return false;
|
|
||||||
if (!(target is IEnumerable enumerable))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
IEnumerable<object> objectList = enumerable.Cast<object>();
|
|
||||||
return ListOperator switch
|
|
||||||
{
|
|
||||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
|
||||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
|
||||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
|
||||||
ListOperator.Count => false,
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Save()
|
|
||||||
{
|
|
||||||
// Don't save an invalid state
|
|
||||||
if (ListPath != null && !ListPath.IsValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Target list
|
|
||||||
ListPath?.Save();
|
|
||||||
Entity.ListPath = ListPath?.Entity;
|
|
||||||
|
|
||||||
// Operator
|
|
||||||
Entity.ListOperator = (int) ListOperator;
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
while (Children.Any())
|
|
||||||
RemoveChild(Children[0]);
|
|
||||||
|
|
||||||
if (Entity.ListPath == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ensure the list path is valid and points to a list
|
|
||||||
DataModelPath listPath = new(null, Entity.ListPath);
|
|
||||||
Type listType = listPath.GetPropertyType()!;
|
|
||||||
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
|
|
||||||
if (listPath.IsValid && !PointsToList(listPath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ListPath = listPath;
|
|
||||||
SubscribeToListPath();
|
|
||||||
if (ListPath.IsValid)
|
|
||||||
{
|
|
||||||
ListType = listType.GetGenericEnumerableType();
|
|
||||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ListType = null;
|
|
||||||
IsPrimitiveList = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 PointsToList(DataModelPath dataModelPath)
|
|
||||||
{
|
|
||||||
Type? type = dataModelPath.GetPropertyType();
|
|
||||||
return type?.IsGenericEnumerable() ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SubscribeToListPath()
|
|
||||||
{
|
|
||||||
if (ListPath == null) return;
|
|
||||||
ListPath.PathValidated += ListPathOnPathValidated;
|
|
||||||
ListPath.PathInvalidated += ListPathOnPathInvalidated;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
|
|
||||||
private void ListPathOnPathValidated(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (_reinitializing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_reinitializing = true;
|
|
||||||
ListPath?.Dispose();
|
|
||||||
Initialize();
|
|
||||||
_reinitializing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ListPathOnPathInvalidated(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (_reinitializing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_reinitializing = true;
|
|
||||||
ListPath?.Dispose();
|
|
||||||
Initialize();
|
|
||||||
_reinitializing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a list operator
|
|
||||||
/// </summary>
|
|
||||||
public enum ListOperator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Any of the list items should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
Any,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All of the list items should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
All,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// None of the list items should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A specific amount of the list items should evaluate to true
|
|
||||||
/// </summary>
|
|
||||||
Count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.Storage.Entities.Profile;
|
|
||||||
using Artemis.Storage.Entities.Profile.Conditions;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
|
|
||||||
/// </summary>
|
|
||||||
public class DataModelConditionListPredicate : DataModelConditionPredicate
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent"></param>
|
|
||||||
/// <param name="predicateType"></param>
|
|
||||||
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
|
||||||
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
|
|
||||||
{
|
|
||||||
DataModelConditionList = null!;
|
|
||||||
ApplyParentList();
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
|
|
||||||
: base(parent, entity)
|
|
||||||
{
|
|
||||||
DataModelConditionList = null!;
|
|
||||||
ApplyParentList();
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data model condition list this predicate belongs to
|
|
||||||
/// </summary>
|
|
||||||
public DataModelConditionList DataModelConditionList { get; private set; }
|
|
||||||
|
|
||||||
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 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
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void InitializeLeftPath()
|
|
||||||
{
|
|
||||||
if (Entity.LeftPath != null)
|
|
||||||
LeftPath = DataModelConditionList.ListType != null
|
|
||||||
? new DataModelPath(ListPredicateWrapperDataModel.Create(
|
|
||||||
DataModelConditionList.ListType,
|
|
||||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
|
||||||
), Entity.LeftPath)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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,
|
|
||||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
|
||||||
), Entity.RightPath)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
// Right side dynamic
|
|
||||||
else
|
|
||||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Modification
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Type? GetPreferredRightSideType()
|
|
||||||
{
|
|
||||||
Type? preferredType = Operator?.RightSideType;
|
|
||||||
Type? leftSideType = DataModelConditionList.IsPrimitiveList
|
|
||||||
? DataModelConditionList.ListType
|
|
||||||
: LeftPath?.GetPropertyType();
|
|
||||||
if (preferredType == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
|
||||||
preferredType = leftSideType;
|
|
||||||
|
|
||||||
return preferredType;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Evaluation
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Not supported for list predicates, always returns <c>false</c>
|
|
||||||
/// </summary>
|
|
||||||
public override bool Evaluate()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override bool EvaluateObject(object? target)
|
|
||||||
{
|
|
||||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Compare with a static value
|
|
||||||
if (PredicateType == ProfileRightSideType.Static)
|
|
||||||
{
|
|
||||||
object? leftSideValue = GetListPathValue(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 list, evaluate on the list path value instead of the right path value
|
|
||||||
if (RightPath.Target is ListPredicateWrapperDataModel)
|
|
||||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
|
|
||||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
211
src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
Normal file
211
src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core.Internal;
|
||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a condition that is based on a <see cref="DataModelEvent" />
|
||||||
|
/// </summary>
|
||||||
|
public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||||
|
{
|
||||||
|
private readonly string _displayName;
|
||||||
|
private readonly EventConditionEntity _entity;
|
||||||
|
private EventDefaultNode? _eventNode;
|
||||||
|
private TimeLineEventOverlapMode _eventOverlapMode;
|
||||||
|
private DataModelPath? _eventPath;
|
||||||
|
private DateTime _lastProcessedTrigger;
|
||||||
|
private NodeScript<bool>? _script;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="EventCondition" /> class
|
||||||
|
/// </summary>
|
||||||
|
public EventCondition(ProfileElement profileElement)
|
||||||
|
{
|
||||||
|
_entity = new EventConditionEntity();
|
||||||
|
_displayName = profileElement.GetType().Name;
|
||||||
|
|
||||||
|
ProfileElement = profileElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal EventCondition(EventConditionEntity entity, ProfileElement profileElement)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
_displayName = profileElement.GetType().Name;
|
||||||
|
|
||||||
|
ProfileElement = profileElement;
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the script that drives the event condition
|
||||||
|
/// </summary>
|
||||||
|
public NodeScript<bool>? Script
|
||||||
|
{
|
||||||
|
get => _script;
|
||||||
|
set => SetAndNotify(ref _script, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the event that drives this event condition
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPath? EventPath
|
||||||
|
{
|
||||||
|
get => _eventPath;
|
||||||
|
set => SetAndNotify(ref _eventPath, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets how the condition behaves when events trigger before the timeline finishes
|
||||||
|
/// </summary>
|
||||||
|
public TimeLineEventOverlapMode EventOverlapMode
|
||||||
|
{
|
||||||
|
get => _eventOverlapMode;
|
||||||
|
set => SetAndNotify(ref _eventOverlapMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the event node, applying the selected event
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateEventNode()
|
||||||
|
{
|
||||||
|
if (Script == null || EventPath?.GetValue() is not IDataModelEvent dataModelEvent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Script.Nodes.FirstOrDefault(n => n is EventDefaultNode) is EventDefaultNode existing)
|
||||||
|
{
|
||||||
|
existing.UpdateDataModelEvent(dataModelEvent);
|
||||||
|
_eventNode = existing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_eventNode = new EventDefaultNode {X = -300};
|
||||||
|
_eventNode.UpdateDataModelEvent(dataModelEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_eventNode.Pins.Any() && !Script.Nodes.Contains(_eventNode))
|
||||||
|
Script.AddNode(_eventNode);
|
||||||
|
else
|
||||||
|
Script.RemoveNode(_eventNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the <see cref="Script" /> with a new empty node script
|
||||||
|
/// </summary>
|
||||||
|
public void CreateEmptyNodeScript()
|
||||||
|
{
|
||||||
|
Script?.Dispose();
|
||||||
|
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
|
||||||
|
UpdateEventNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Evaluate()
|
||||||
|
{
|
||||||
|
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_lastProcessedTrigger = dataModelEvent.LastTrigger;
|
||||||
|
|
||||||
|
if (Script == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Script.Run();
|
||||||
|
return Script.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IConditionEntity Entity => _entity;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ProfileElement ProfileElement { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsMet { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (EventOverlapMode == TimeLineEventOverlapMode.Toggle)
|
||||||
|
{
|
||||||
|
if (Evaluate())
|
||||||
|
IsMet = !IsMet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsMet = Evaluate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
|
||||||
|
{
|
||||||
|
if (!isMet)
|
||||||
|
{
|
||||||
|
if (EventOverlapMode == TimeLineEventOverlapMode.Toggle)
|
||||||
|
timeline.JumpToEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event overlap mode doesn't apply in this case
|
||||||
|
if (timeline.IsFinished)
|
||||||
|
{
|
||||||
|
timeline.JumpToStart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the timeline was already running, look at the event overlap mode
|
||||||
|
if (EventOverlapMode == TimeLineEventOverlapMode.Restart)
|
||||||
|
timeline.JumpToStart();
|
||||||
|
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy)
|
||||||
|
timeline.AddExtraTimeline();
|
||||||
|
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
|
||||||
|
timeline.JumpToStart();
|
||||||
|
|
||||||
|
// The remaining overlap mode is 'ignore' which requires no further action
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Script?.Dispose();
|
||||||
|
EventPath?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Storage
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode;
|
||||||
|
if (_entity.Script != null)
|
||||||
|
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile);
|
||||||
|
if (_entity.EventPath != null)
|
||||||
|
EventPath = new DataModelPath(_entity.EventPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_entity.EventOverlapMode = (int) EventOverlapMode;
|
||||||
|
Script?.Save();
|
||||||
|
_entity.Script = Script?.Entity;
|
||||||
|
EventPath?.Save();
|
||||||
|
_entity.EventPath = EventPath?.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void LoadNodeScript()
|
||||||
|
{
|
||||||
|
if (Script == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Script.Load();
|
||||||
|
UpdateEventNode();
|
||||||
|
Script.LoadConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/Artemis.Core/Models/Profile/Conditions/ICondition.cs
Normal file
51
src/Artemis.Core/Models/Profile/Conditions/ICondition.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a condition applied to a <see cref="ProfileElement" />
|
||||||
|
/// </summary>
|
||||||
|
public interface ICondition : IDisposable, IStorageModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity used to store this condition
|
||||||
|
/// </summary>
|
||||||
|
public IConditionEntity Entity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile element this condition applies to
|
||||||
|
/// </summary>
|
||||||
|
public ProfileElement ProfileElement { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the condition is currently met
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
bool IsMet { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the condition
|
||||||
|
/// </summary>
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the display condition to the provided timeline
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMet"></param>
|
||||||
|
/// <param name="wasMet"></param>
|
||||||
|
/// <param name="timeline">The timeline to apply the display condition to</param>
|
||||||
|
void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a condition applied to a <see cref="ProfileElement" /> using a <see cref="INodeScript" />
|
||||||
|
/// </summary>
|
||||||
|
public interface INodeScriptCondition : ICondition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the node script this node script condition uses
|
||||||
|
/// </summary>
|
||||||
|
void LoadNodeScript();
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs
Normal file
102
src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a condition that is based on a data model value
|
||||||
|
/// </summary>
|
||||||
|
public class StaticCondition : CorePropertyChanged, INodeScriptCondition
|
||||||
|
{
|
||||||
|
private readonly string _displayName;
|
||||||
|
private readonly StaticConditionEntity _entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="StaticCondition" /> class
|
||||||
|
/// </summary>
|
||||||
|
public StaticCondition(ProfileElement profileElement)
|
||||||
|
{
|
||||||
|
_entity = new StaticConditionEntity();
|
||||||
|
_displayName = profileElement.GetType().Name;
|
||||||
|
|
||||||
|
ProfileElement = profileElement;
|
||||||
|
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", profileElement.Profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal StaticCondition(StaticConditionEntity entity, ProfileElement profileElement)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
_displayName = profileElement.GetType().Name;
|
||||||
|
|
||||||
|
ProfileElement = profileElement;
|
||||||
|
Script = null!;
|
||||||
|
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the script that drives the static condition
|
||||||
|
/// </summary>
|
||||||
|
public NodeScript<bool> Script { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IConditionEntity Entity => _entity;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ProfileElement ProfileElement { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsMet { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!Script.ExitNodeConnected)
|
||||||
|
{
|
||||||
|
IsMet = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.Run();
|
||||||
|
IsMet = Script.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
|
||||||
|
{
|
||||||
|
if (isMet && !wasMet && timeline.IsFinished)
|
||||||
|
timeline.JumpToStart();
|
||||||
|
else if (!isMet && wasMet && timeline.StopMode == TimelineStopMode.SkipToEnd)
|
||||||
|
timeline.JumpToEndSegment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Script.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Storage
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
Script.Save();
|
||||||
|
_entity.Script = Script.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void LoadNodeScript()
|
||||||
|
{
|
||||||
|
Script.Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,40 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
Module = Constants.CorePluginFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last arguments of this event as an object
|
|
||||||
/// </summary>
|
|
||||||
[DataModelIgnore]
|
|
||||||
public object? UntypedArguments { get; internal 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
internal class ListPredicateWrapperDataModel<T> : ListPredicateWrapperDataModel
|
|
||||||
{
|
|
||||||
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a datamodel that wraps a value in a list
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ListPredicateWrapperDataModel : DataModel
|
|
||||||
{
|
|
||||||
internal ListPredicateWrapperDataModel()
|
|
||||||
{
|
|
||||||
Module = Constants.CorePluginFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the value of this list as an object
|
|
||||||
/// </summary>
|
|
||||||
[DataModelIgnore]
|
|
||||||
public object? UntypedValue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the list item
|
|
||||||
/// </summary>
|
|
||||||
[DataModelIgnore]
|
|
||||||
public string? ItemName { get; set; }
|
|
||||||
|
|
||||||
#region Overrides of DataModel
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(ItemName))
|
|
||||||
return new DataModelPropertyAttribute {Name = ItemName};
|
|
||||||
return base.GetPropertyDescription(propertyInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel" /> class
|
|
||||||
/// </summary>
|
|
||||||
public static ListPredicateWrapperDataModel Create(Type type, string? name = null)
|
|
||||||
{
|
|
||||||
ListPredicateWrapperDataModel? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type)) as ListPredicateWrapperDataModel;
|
|
||||||
if (instance == null)
|
|
||||||
throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel<T> for type {type.Name}");
|
|
||||||
|
|
||||||
instance.ItemName = name;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,67 +1,49 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class DataBinding<TLayerProperty, TProperty> : IDataBinding
|
public class DataBinding<TLayerProperty> : IDataBinding
|
||||||
{
|
{
|
||||||
private TProperty _currentValue = default!;
|
private readonly List<IDataBindingProperty> _properties = new();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private TimeSpan _easingProgress;
|
private bool _isEnabled;
|
||||||
private TProperty _lastAppliedValue = default!;
|
private DataBindingNodeScript<TLayerProperty> _script;
|
||||||
private TProperty _previousValue = default!;
|
|
||||||
private bool _reapplyValue;
|
|
||||||
|
|
||||||
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
|
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
|
||||||
{
|
{
|
||||||
LayerProperty = dataBindingRegistration.LayerProperty;
|
LayerProperty = layerProperty;
|
||||||
Entity = new DataBindingEntity();
|
|
||||||
|
Entity = new DataBindingEntity();
|
||||||
|
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||||
|
|
||||||
ApplyRegistration(dataBindingRegistration);
|
|
||||||
Save();
|
Save();
|
||||||
ApplyDataBindingMode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
|
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
|
||||||
{
|
{
|
||||||
LayerProperty = layerProperty;
|
LayerProperty = layerProperty;
|
||||||
|
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
|
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||||
|
|
||||||
// Load will add children so be initialized before that
|
// Load will add children so be initialized before that
|
||||||
Load();
|
Load();
|
||||||
ApplyDataBindingMode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data binding registration this data binding is based upon
|
|
||||||
/// </summary>
|
|
||||||
public DataBindingRegistration<TLayerProperty, TProperty>? Registration { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the layer property this data binding targets
|
/// Gets the layer property this data binding targets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LayerProperty<TLayerProperty> LayerProperty { get; }
|
public LayerProperty<TLayerProperty> LayerProperty { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the converter used to apply this data binding to the <see cref="LayerProperty" />
|
/// Gets the script used to populate the data binding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
|
public INodeScript Script => _script;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data binding mode
|
|
||||||
/// </summary>
|
|
||||||
public IDataBindingMode<TLayerProperty, TProperty>? DataBindingMode { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the easing time of the data binding
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan EasingTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets ors ets the easing function of the data binding
|
|
||||||
/// </summary>
|
|
||||||
public Easings.Functions EasingFunction { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data binding entity this data binding uses for persistent storage
|
/// Gets the data binding entity this data binding uses for persistent storage
|
||||||
@ -69,38 +51,52 @@ namespace Artemis.Core
|
|||||||
public DataBindingEntity Entity { get; }
|
public DataBindingEntity Entity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current value of the data binding
|
/// Updates the pending values of this data binding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
|
public void Update()
|
||||||
/// <returns></returns>
|
|
||||||
public TProperty GetValue(TProperty baseValue)
|
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("DataBinding");
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
|
||||||
if (Converter == null || DataBindingMode == null)
|
if (!IsEnabled)
|
||||||
return baseValue;
|
return;
|
||||||
|
|
||||||
TProperty value = DataBindingMode.GetValue(baseValue);
|
// TODO: Update the 'base value' node
|
||||||
|
|
||||||
// If no easing is to be applied simple return whatever the current value is
|
Script.Run();
|
||||||
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
|
|
||||||
return value;
|
|
||||||
|
|
||||||
// If the value changed, update the current and previous values used for easing
|
|
||||||
if (!Equals(value, _currentValue))
|
|
||||||
ResetEasing(value);
|
|
||||||
|
|
||||||
// Apply interpolation between the previous and current value
|
|
||||||
return GetInterpolatedValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the type of the target property of this data binding
|
/// Registers a data binding property so that is available to the data binding system
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type? GetTargetType()
|
/// <typeparam name="TProperty">The type of the layer property</typeparam>
|
||||||
|
/// <param name="getter">The function to call to get the value of the property</param>
|
||||||
|
/// <param name="setter">The action to call to set the value of the property</param>
|
||||||
|
/// <param name="displayName">The display name of the data binding property</param>
|
||||||
|
public DataBindingProperty<TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||||
{
|
{
|
||||||
return Registration?.Getter.Method.ReturnType;
|
if (_disposed)
|
||||||
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
if (Properties.Any(d => d.DisplayName == displayName))
|
||||||
|
throw new ArtemisCoreException($"A data binding property named '{displayName}' is already registered.");
|
||||||
|
|
||||||
|
DataBindingProperty<TProperty> property = new(getter, setter, displayName);
|
||||||
|
_properties.Add(property);
|
||||||
|
|
||||||
|
OnDataBindingPropertyRegistered();
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all data binding properties so they are no longer available to the data binding system
|
||||||
|
/// </summary>
|
||||||
|
public void ClearDataBindingProperties()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
throw new ObjectDisposedException("LayerProperty");
|
||||||
|
|
||||||
|
_properties.Clear();
|
||||||
|
OnDataBindingPropertiesCleared();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -115,103 +111,80 @@ namespace Artemis.Core
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
_isEnabled = false;
|
||||||
|
|
||||||
if (Registration != null)
|
Script.Dispose();
|
||||||
Registration.DataBinding = null;
|
|
||||||
DataBindingMode?.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetEasing(TProperty value)
|
|
||||||
{
|
|
||||||
_previousValue = GetInterpolatedValue();
|
|
||||||
_currentValue = value;
|
|
||||||
_easingProgress = TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyRegistration(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
|
|
||||||
{
|
|
||||||
if (dataBindingRegistration == null)
|
|
||||||
throw new ArgumentNullException(nameof(dataBindingRegistration));
|
|
||||||
|
|
||||||
dataBindingRegistration.DataBinding = this;
|
|
||||||
Converter = dataBindingRegistration.Converter;
|
|
||||||
Registration = dataBindingRegistration;
|
|
||||||
|
|
||||||
if (GetTargetType()!.IsValueType)
|
|
||||||
{
|
|
||||||
if (_currentValue == null)
|
|
||||||
_currentValue = default!;
|
|
||||||
if (_previousValue == null)
|
|
||||||
_previousValue = default!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Converter?.Initialize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TProperty GetInterpolatedValue()
|
|
||||||
{
|
|
||||||
if (_easingProgress == EasingTime || Converter == null || !Converter.SupportsInterpolate)
|
|
||||||
return _currentValue;
|
|
||||||
|
|
||||||
double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds;
|
|
||||||
return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the smoothing progress of the data binding
|
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timeline">The timeline to apply during update</param>
|
protected virtual void OnDataBindingPropertyRegistered()
|
||||||
public void Update(Timeline timeline)
|
|
||||||
{
|
{
|
||||||
// Don't update data bindings if there is no delta, otherwise this creates an inconsistency between
|
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
|
||||||
// data bindings with easing and data bindings without easing (the ones with easing will seemingly not update)
|
}
|
||||||
if (timeline.Delta == TimeSpan.Zero || timeline.IsOverridden)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UpdateWithDelta(timeline.Delta);
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="DataBindingDisabled" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnDataBindingPropertiesCleared()
|
||||||
|
{
|
||||||
|
DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="DataBindingEnabled" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnDataBindingEnabled(DataBindingEventArgs e)
|
||||||
|
{
|
||||||
|
DataBindingEnabled?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="DataBindingDisabled" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnDataBindingDisabled(DataBindingEventArgs e)
|
||||||
|
{
|
||||||
|
DataBindingDisabled?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetScriptName()
|
||||||
|
{
|
||||||
|
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void UpdateWithDelta(TimeSpan delta)
|
public ILayerProperty BaseLayerProperty => LayerProperty;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled
|
||||||
{
|
{
|
||||||
if (_disposed)
|
get => _isEnabled;
|
||||||
throw new ObjectDisposedException("DataBinding");
|
set
|
||||||
|
{
|
||||||
|
_isEnabled = value;
|
||||||
|
|
||||||
// Data bindings cannot go back in time like brushes
|
if (_isEnabled)
|
||||||
if (delta < TimeSpan.Zero)
|
OnDataBindingEnabled(new DataBindingEventArgs(this));
|
||||||
delta = TimeSpan.Zero;
|
else
|
||||||
|
OnDataBindingDisabled(new DataBindingEventArgs(this));
|
||||||
_easingProgress = _easingProgress.Add(delta);
|
}
|
||||||
if (_easingProgress > EasingTime)
|
|
||||||
_easingProgress = EasingTime;
|
|
||||||
|
|
||||||
// Tell Apply() to apply a new value next call
|
|
||||||
_reapplyValue = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply()
|
public void Apply()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("DataBinding");
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
|
||||||
if (Converter == null)
|
if (!IsEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If Update() has not been called, reapply the previous value
|
_script.DataBindingExitNode.ApplyToDataBinding();
|
||||||
if (_reapplyValue)
|
|
||||||
{
|
|
||||||
Converter.ApplyValue(_lastAppliedValue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TProperty converterValue = Converter.GetValue();
|
|
||||||
TProperty value = GetValue(converterValue);
|
|
||||||
Converter.ApplyValue(value);
|
|
||||||
|
|
||||||
_lastAppliedValue = value;
|
|
||||||
_reapplyValue = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -221,56 +194,18 @@ namespace Artemis.Core
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Mode management
|
/// <inheritdoc />
|
||||||
|
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Changes the data binding mode of the data binding to the specified <paramref name="dataBindingMode" />
|
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||||
/// </summary>
|
|
||||||
public void ChangeDataBindingMode(DataBindingModeType dataBindingMode)
|
|
||||||
{
|
|
||||||
switch (dataBindingMode)
|
|
||||||
{
|
|
||||||
case DataBindingModeType.Direct:
|
|
||||||
Entity.DataBindingMode = new DirectDataBindingEntity();
|
|
||||||
break;
|
|
||||||
case DataBindingModeType.Conditional:
|
|
||||||
Entity.DataBindingMode = new ConditionalDataBindingEntity();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Entity.DataBindingMode = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyDataBindingMode();
|
/// <inheritdoc />
|
||||||
}
|
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Replaces the current data binding mode with one based on the provided data binding mode entity
|
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataBindingModeEntity">The data binding mode entity to base the new data binding mode upon</param>
|
|
||||||
public void ApplyDataBindingEntity(IDataBindingModeEntity dataBindingModeEntity)
|
|
||||||
{
|
|
||||||
Entity.DataBindingMode = dataBindingModeEntity;
|
|
||||||
ApplyDataBindingMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyDataBindingMode()
|
|
||||||
{
|
|
||||||
DataBindingMode?.Dispose();
|
|
||||||
DataBindingMode = null;
|
|
||||||
|
|
||||||
switch (Entity.DataBindingMode)
|
|
||||||
{
|
|
||||||
case DirectDataBindingEntity directDataBindingEntity:
|
|
||||||
DataBindingMode = new DirectDataBinding<TLayerProperty, TProperty>(this, directDataBindingEntity);
|
|
||||||
break;
|
|
||||||
case ConditionalDataBindingEntity conditionalDataBindingEntity:
|
|
||||||
DataBindingMode = new ConditionalDataBinding<TLayerProperty, TProperty>(this, conditionalDataBindingEntity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Storage
|
#region Storage
|
||||||
|
|
||||||
@ -280,15 +215,16 @@ namespace Artemis.Core
|
|||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("DataBinding");
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
|
||||||
// General
|
IsEnabled = Entity.IsEnabled;
|
||||||
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
|
}
|
||||||
if (registration != null)
|
|
||||||
ApplyRegistration(registration);
|
|
||||||
|
|
||||||
EasingTime = Entity.EasingTime;
|
/// <inheritdoc />
|
||||||
EasingFunction = (Easings.Functions) Entity.EasingFunction;
|
public void LoadNodeScript()
|
||||||
|
{
|
||||||
DataBindingMode?.Load();
|
_script.Dispose();
|
||||||
|
_script = Entity.NodeScript != null
|
||||||
|
? new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile)
|
||||||
|
: new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -297,40 +233,11 @@ namespace Artemis.Core
|
|||||||
if (_disposed)
|
if (_disposed)
|
||||||
throw new ObjectDisposedException("DataBinding");
|
throw new ObjectDisposedException("DataBinding");
|
||||||
|
|
||||||
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
|
_script.Save();
|
||||||
LayerProperty.Entity.DataBindingEntities.Add(Entity);
|
Entity.IsEnabled = IsEnabled;
|
||||||
|
Entity.NodeScript = _script.Entity.Nodes.Any() ? _script.Entity : null;
|
||||||
// Don't save an invalid state
|
|
||||||
if (Registration != null)
|
|
||||||
Entity.Identifier = Registration.DisplayName;
|
|
||||||
|
|
||||||
Entity.EasingTime = EasingTime;
|
|
||||||
Entity.EasingFunction = (int) EasingFunction;
|
|
||||||
|
|
||||||
DataBindingMode?.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A mode that determines how the data binding is applied to the layer property
|
|
||||||
/// </summary>
|
|
||||||
public enum DataBindingModeType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Disables the data binding
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Replaces the layer property value with the data binding value
|
|
||||||
/// </summary>
|
|
||||||
Direct,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Replaces the layer property value with the data binding value
|
|
||||||
/// </summary>
|
|
||||||
Conditional
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,90 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a data binding converter that acts as the bridge between a
|
|
||||||
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data binding this converter is applied to
|
|
||||||
/// </summary>
|
|
||||||
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether or not this data binding converter supports the <see cref="Sum" /> method
|
|
||||||
/// </summary>
|
|
||||||
public bool SupportsSum { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether or not this data binding converter supports the <see cref="Interpolate" /> method
|
|
||||||
/// </summary>
|
|
||||||
public bool SupportsInterpolate { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
|
|
||||||
/// </summary>
|
|
||||||
public abstract TProperty Sum(TProperty a, TProperty b);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the the interpolated value between <paramref name="a" /> and <paramref name="b" /> on a scale (generally)
|
|
||||||
/// between <c>0.0</c> and <c>1.0</c> defined by the <paramref name="progress" />
|
|
||||||
/// <para>Note: The progress may go be negative or go beyond <c>1.0</c> depending on the easing method used</para>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="a">The value to interpolate away from</param>
|
|
||||||
/// <param name="b">The value to interpolate towards</param>
|
|
||||||
/// <param name="progress">The progress of the interpolation between 0.0 and 1.0</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public abstract TProperty Interpolate(TProperty a, TProperty b, double progress);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies the <paramref name="value" /> to the layer property
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
public virtual void ApplyValue(TProperty value)
|
|
||||||
{
|
|
||||||
if (DataBinding?.Registration == null)
|
|
||||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
|
||||||
DataBinding.Registration.Setter(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the current base value of the data binding
|
|
||||||
/// </summary>
|
|
||||||
public virtual TProperty GetValue()
|
|
||||||
{
|
|
||||||
if (DataBinding?.Registration == null)
|
|
||||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
|
||||||
return DataBinding.Registration.Getter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the provided object to a type of <typeparamref name="TProperty" />
|
|
||||||
/// </summary>
|
|
||||||
public virtual TProperty ConvertFromObject(object? source)
|
|
||||||
{
|
|
||||||
return (TProperty) Convert.ChangeType(source, typeof(TProperty))!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the data binding converter has been initialized and the <see cref="DataBinding" /> is available
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnInitialized()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding)
|
|
||||||
{
|
|
||||||
if (dataBinding.Registration == null)
|
|
||||||
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
|
|
||||||
|
|
||||||
DataBinding = dataBinding;
|
|
||||||
OnInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Type SupportedType => typeof(TProperty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class DataBindingProperty<TProperty> : IDataBindingProperty
|
||||||
|
{
|
||||||
|
internal DataBindingProperty(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
|
||||||
|
{
|
||||||
|
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
||||||
|
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
||||||
|
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the function to call to get the value of the property
|
||||||
|
/// </summary>
|
||||||
|
public Func<TProperty> Getter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the action to call to set the value of the property
|
||||||
|
/// </summary>
|
||||||
|
public Action<TProperty?> Setter { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Type ValueType => typeof(TProperty);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object? GetValue()
|
||||||
|
{
|
||||||
|
return Getter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetValue(object? value)
|
||||||
|
{
|
||||||
|
if (value is TProperty match)
|
||||||
|
Setter(match);
|
||||||
|
else
|
||||||
|
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,78 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
|
|
||||||
{
|
|
||||||
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
|
|
||||||
Func<TProperty> getter, Action<TProperty> setter, string displayName)
|
|
||||||
{
|
|
||||||
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
|
|
||||||
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
|
|
||||||
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
|
||||||
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
|
||||||
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the layer property this registration was made on
|
|
||||||
/// </summary>
|
|
||||||
public LayerProperty<TLayerProperty> LayerProperty { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the converter that's used by the data binding
|
|
||||||
/// </summary>
|
|
||||||
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the function to call to get the value of the property
|
|
||||||
/// </summary>
|
|
||||||
public Func<TProperty> Getter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the action to call to set the value of the property
|
|
||||||
/// </summary>
|
|
||||||
public Action<TProperty> Setter { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string DisplayName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data binding created using this registration
|
|
||||||
/// </summary>
|
|
||||||
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; internal set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IDataBinding? GetDataBinding()
|
|
||||||
{
|
|
||||||
return DataBinding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IDataBinding? CreateDataBinding()
|
|
||||||
{
|
|
||||||
if (DataBinding != null)
|
|
||||||
return DataBinding;
|
|
||||||
|
|
||||||
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName);
|
|
||||||
if (dataBinding == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding);
|
|
||||||
return DataBinding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ClearDataBinding()
|
|
||||||
{
|
|
||||||
if (DataBinding == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The related entity is left behind, just in case the data binding is added back later
|
|
||||||
LayerProperty.DisableDataBinding(DataBinding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -7,17 +8,51 @@ namespace Artemis.Core
|
|||||||
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
|
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
|
||||||
/// <see cref="DataModel" />
|
/// <see cref="DataModel" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable
|
public interface IDataBinding : IStorageModel, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the smoothing progress of the data binding and recalculates the value next <see cref="Apply" /> call
|
/// Gets the layer property the data binding is applied to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="delta">The delta to apply during update</param>
|
ILayerProperty BaseLayerProperty { get; }
|
||||||
void UpdateWithDelta(TimeSpan delta);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the data binding to the layer property
|
/// Gets a list of sub-properties this data binding applies to
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlyCollection<IDataBindingProperty> Properties { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the data binding is enabled or not
|
||||||
|
/// </summary>
|
||||||
|
bool IsEnabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the pending value of the data binding to the property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Apply();
|
void Apply();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the data binding is enabled, loads the node script for that data binding
|
||||||
|
/// </summary>
|
||||||
|
void LoadNodeScript();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a data binding property has been added
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when all data binding properties have been removed
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a data binding has been enabled
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a data binding has been disabled
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a data binding converter that acts as the bridge between a
|
|
||||||
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
|
|
||||||
/// </summary>
|
|
||||||
public interface IDataBindingConverter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type this converter supports
|
|
||||||
/// </summary>
|
|
||||||
public Type SupportedType { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a data binding registration
|
||||||
|
/// </summary>
|
||||||
|
public interface IDataBindingProperty
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the display name of the data binding registration
|
||||||
|
/// </summary>
|
||||||
|
string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the value this data binding registration points to
|
||||||
|
/// </summary>
|
||||||
|
Type ValueType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the property this registration points to
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A value matching the type of <see cref="ValueType" /></returns>
|
||||||
|
object? GetValue();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of the property this registration points to
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
|
||||||
|
void SetValue(object? value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a data binding registration
|
|
||||||
/// </summary>
|
|
||||||
public interface IDataBindingRegistration
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the display name of the data binding registration
|
|
||||||
/// </summary>
|
|
||||||
string DisplayName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the data binding applied using this registration
|
|
||||||
/// </summary>
|
|
||||||
public IDataBinding? GetDataBinding();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If found, creates a data binding from storage
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
IDataBinding? CreateDataBinding();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If present, removes the current data binding
|
|
||||||
/// </summary>
|
|
||||||
void ClearDataBinding();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a data binding mode that applies a value depending on conditions
|
|
||||||
/// </summary>
|
|
||||||
public class ConditionalDataBinding<TLayerProperty, TProperty> : IDataBindingMode<TLayerProperty, TProperty>
|
|
||||||
{
|
|
||||||
private readonly List<DataBindingCondition<TLayerProperty, TProperty>> _conditions = new();
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
internal ConditionalDataBinding(DataBinding<TLayerProperty, TProperty> dataBinding, ConditionalDataBindingEntity entity)
|
|
||||||
{
|
|
||||||
DataBinding = dataBinding;
|
|
||||||
Entity = entity;
|
|
||||||
Conditions = new ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>>(_conditions);
|
|
||||||
Load();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of conditions applied to this data binding
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions { get; }
|
|
||||||
|
|
||||||
internal ConditionalDataBindingEntity Entity { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public TProperty GetValue(TProperty baseValue)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
|
||||||
|
|
||||||
DataBindingCondition<TLayerProperty, TProperty>? condition = Conditions.FirstOrDefault(c => c.Evaluate());
|
|
||||||
return condition == null ? baseValue : condition.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">
|
|
||||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
|
||||||
/// <see langword="false" /> to release only unmanaged resources.
|
|
||||||
/// </param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
foreach (DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition in Conditions)
|
|
||||||
dataBindingCondition.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Values
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a condition to the conditional data binding's <see cref="Conditions" /> collection
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The newly created <see cref="DataBindingCondition{TLayerProperty,TProperty}" /></returns>
|
|
||||||
public DataBindingCondition<TLayerProperty, TProperty> AddCondition()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
|
||||||
|
|
||||||
DataBindingCondition<TLayerProperty, TProperty> condition = new(this);
|
|
||||||
_conditions.Add(condition);
|
|
||||||
|
|
||||||
ApplyOrder();
|
|
||||||
OnConditionsUpdated();
|
|
||||||
|
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a condition from the conditional data binding's <see cref="Conditions" /> collection and disposes it
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="condition"></param>
|
|
||||||
public void RemoveCondition(DataBindingCondition<TLayerProperty, TProperty> condition)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
|
||||||
if (!_conditions.Contains(condition))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_conditions.Remove(condition);
|
|
||||||
condition.Dispose();
|
|
||||||
|
|
||||||
ApplyOrder();
|
|
||||||
OnConditionsUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies the current order of conditions to the <see cref="Conditions" /> collection
|
|
||||||
/// </summary>
|
|
||||||
public void ApplyOrder()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
|
||||||
|
|
||||||
_conditions.Sort((a, b) => a.Order.CompareTo(b.Order));
|
|
||||||
for (int index = 0; index < _conditions.Count; index++)
|
|
||||||
{
|
|
||||||
DataBindingCondition<TLayerProperty, TProperty> condition = _conditions[index];
|
|
||||||
condition.Order = index + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Storage
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Load()
|
|
||||||
{
|
|
||||||
foreach (DataBindingConditionEntity dataBindingConditionEntity in Entity.Values)
|
|
||||||
_conditions.Add(new DataBindingCondition<TLayerProperty, TProperty>(this, dataBindingConditionEntity));
|
|
||||||
|
|
||||||
ApplyOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
Entity.Values.Clear();
|
|
||||||
foreach (DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition in Conditions)
|
|
||||||
dataBindingCondition.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a condition is added or removed
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? ConditionsUpdated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="ConditionsUpdated" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnConditionsUpdated()
|
|
||||||
{
|
|
||||||
ConditionsUpdated?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class DataBindingCondition<TLayerProperty, TProperty> : IDataBindingCondition
|
|
||||||
{
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DataBindingCondition{TLayerProperty,TProperty}" /> class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="conditionalDataBinding">The conditional data binding this condition is applied too</param>
|
|
||||||
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding)
|
|
||||||
{
|
|
||||||
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
|
|
||||||
Order = conditionalDataBinding.Conditions.Count + 1;
|
|
||||||
Condition = new DataModelConditionGroup(null);
|
|
||||||
Value = default!;
|
|
||||||
|
|
||||||
Entity = new DataBindingConditionEntity();
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding, DataBindingConditionEntity entity)
|
|
||||||
{
|
|
||||||
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
|
|
||||||
Entity = entity;
|
|
||||||
Condition = null!;
|
|
||||||
Value = default!;
|
|
||||||
|
|
||||||
Load();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the conditional data binding this condition is applied to
|
|
||||||
/// </summary>
|
|
||||||
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the position at which the modifier appears on the data binding
|
|
||||||
/// </summary>
|
|
||||||
public int Order { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the value to be applied when the condition is met
|
|
||||||
/// </summary>
|
|
||||||
public TProperty Value { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the root group of the condition that must be met
|
|
||||||
/// </summary>
|
|
||||||
public DataModelConditionGroup Condition { get; private set; }
|
|
||||||
|
|
||||||
internal DataBindingConditionEntity Entity { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Evaluate()
|
|
||||||
{
|
|
||||||
return Condition.Evaluate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataBindingCondition");
|
|
||||||
|
|
||||||
if (!ConditionalDataBinding.Entity.Values.Contains(Entity))
|
|
||||||
ConditionalDataBinding.Entity.Values.Add(Entity);
|
|
||||||
|
|
||||||
Entity.Condition = Condition.Entity;
|
|
||||||
Condition.Save();
|
|
||||||
|
|
||||||
Entity.Value = CoreJson.SerializeObject(Value);
|
|
||||||
Entity.Order = Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Load()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
throw new ObjectDisposedException("DataBindingCondition");
|
|
||||||
|
|
||||||
Condition = Entity.Condition != null
|
|
||||||
? new DataModelConditionGroup(null, Entity.Condition)
|
|
||||||
: new DataModelConditionGroup(null);
|
|
||||||
|
|
||||||
Value = (Entity.Value == null ? default : CoreJson.DeserializeObject<TProperty>(Entity.Value))!;
|
|
||||||
Order = Entity.Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">
|
|
||||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
|
||||||
/// <see langword="false" /> to release only unmanaged resources.
|
|
||||||
/// </param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
Condition.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Artemis.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a condition and a value inside a <see cref="ConditionalDataBinding{TLayerProperty,TProperty}" />
|
|
||||||
/// </summary>
|
|
||||||
public interface IDataBindingCondition : IStorageModel, IDisposable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the condition
|
|
||||||
/// </summary>
|
|
||||||
bool Evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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