1
0
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:
Robert 2021-11-17 23:07:36 +01:00
commit 4ef7c83c74
366 changed files with 11987 additions and 10867 deletions

View File

@ -84,4 +84,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="DefaultTypes\DataBindings\" />
</ItemGroup>
</Project>

View File

@ -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">
<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_005Cprofile_005Cadaption/@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_005Cdatamodel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel_005Cvaluechangedevent/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdisplay/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Cattributes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>
<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_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/=placeholders/@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/=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/=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>

View File

@ -77,13 +77,13 @@ namespace Artemis.Core
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()
{
TypeNameHandling = TypeNameHandling.All,
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
};
/// <summary>

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -4,9 +4,14 @@
public class BoolLayerProperty : LayerProperty<bool>
{
internal BoolLayerProperty()
{
}
/// <inheritdoc />
protected override void OnInitialize()
{
KeyframesSupported = false;
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -12,7 +12,6 @@ namespace Artemis.Core
internal ColorGradientLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = true;
DefaultValue = new ColorGradient();
CurrentValueSet += OnCurrentValueSet;
@ -20,7 +19,7 @@ namespace Artemis.Core
private void CreateDataBindingRegistrations()
{
ClearDataBindingProperties();
DataBinding.ClearDataBindingProperties();
if (CurrentValue == null)
return;
@ -33,7 +32,7 @@ namespace Artemis.Core
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)
{
if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
if (CurrentValue.Count != DataBinding.Properties.Count)
CreateDataBindingRegistrations();
}
@ -89,25 +88,4 @@ namespace Artemis.Core
#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);
}
}
}

View File

@ -8,7 +8,6 @@ namespace Artemis.Core
internal EnumLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = false;
}
/// <summary>

View File

@ -5,7 +5,12 @@
{
internal FloatLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -5,13 +5,17 @@
{
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;
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 />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{

View File

@ -7,7 +7,12 @@ namespace Artemis.Core
{
internal IntLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -5,13 +5,17 @@
{
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;
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 />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{

View File

@ -8,7 +8,6 @@
internal LayerBrushReferenceLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = false;
}
/// <summary>

View File

@ -7,7 +7,12 @@ namespace Artemis.Core
{
internal SKColorLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -7,8 +7,13 @@ namespace Artemis.Core
{
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>

View File

@ -7,8 +7,13 @@ namespace Artemis.Core
{
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>

View 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; }
}
}

View File

@ -1,12 +0,0 @@
namespace Artemis.Core
{
internal class ConditionOperatorStoreEvent
{
public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration)
{
Registration = registration;
}
public ConditionOperatorRegistration Registration { get; }
}
}

View File

@ -1,12 +0,0 @@
namespace Artemis.Core
{
internal class DataBindingModifierTypeStoreEvent
{
public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration)
{
TypeRegistration = typeRegistration;
}
public DataBindingModifierTypeRegistration TypeRegistration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Core
{
internal class NodeTypeStoreEvent
{
public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration)
{
TypeRegistration = typeRegistration;
}
public NodeTypeRegistration TypeRegistration { get; }
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{
return (byte) Math.Clamp(value, 0, 255);

View File

@ -130,8 +130,11 @@ namespace Artemis.Core
/// at all
/// </summary>
/// <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)
return 5;
if (to.TypeIsNumber() && from.TypeIsNumber())

View 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
}
}

View File

@ -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
}
}

View File

@ -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!);
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View 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
}
}

View File

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

View File

@ -0,0 +1,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
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,67 +1,49 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <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 TimeSpan _easingProgress;
private TProperty _lastAppliedValue = default!;
private TProperty _previousValue = default!;
private bool _reapplyValue;
private bool _isEnabled;
private DataBindingNodeScript<TLayerProperty> _script;
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
{
LayerProperty = dataBindingRegistration.LayerProperty;
Entity = new DataBindingEntity();
LayerProperty = layerProperty;
Entity = new DataBindingEntity();
_script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
ApplyRegistration(dataBindingRegistration);
Save();
ApplyDataBindingMode();
}
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
{
LayerProperty = layerProperty;
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();
ApplyDataBindingMode();
}
/// <summary>
/// Gets the data binding registration this data binding is based upon
/// </summary>
public DataBindingRegistration<TLayerProperty, TProperty>? Registration { get; private set; }
/// <summary>
/// Gets the layer property this data binding targets
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <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>
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
/// <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; }
public INodeScript Script => _script;
/// <summary>
/// Gets the data binding entity this data binding uses for persistent storage
@ -69,38 +51,52 @@ namespace Artemis.Core
public DataBindingEntity Entity { get; }
/// <summary>
/// Gets the current value of the data binding
/// Updates the pending values of this data binding
/// </summary>
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
/// <returns></returns>
public TProperty GetValue(TProperty baseValue)
public void Update()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null || DataBindingMode == null)
return baseValue;
if (!IsEnabled)
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
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();
Script.Run();
}
/// <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>
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>
@ -115,103 +111,80 @@ namespace Artemis.Core
if (disposing)
{
_disposed = true;
_isEnabled = false;
if (Registration != null)
Registration.DataBinding = null;
DataBindingMode?.Dispose();
Script.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>
/// Updates the smoothing progress of the data binding
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
/// <param name="timeline">The timeline to apply during update</param>
public void Update(Timeline timeline)
protected virtual void OnDataBindingPropertyRegistered()
{
// Don't update data bindings if there is no delta, otherwise this creates an inconsistency between
// 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;
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
}
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 />
public void UpdateWithDelta(TimeSpan delta)
public ILayerProperty BaseLayerProperty => LayerProperty;
/// <inheritdoc />
public bool IsEnabled
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
get => _isEnabled;
set
{
_isEnabled = value;
// Data bindings cannot go back in time like brushes
if (delta < TimeSpan.Zero)
delta = TimeSpan.Zero;
_easingProgress = _easingProgress.Add(delta);
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
// Tell Apply() to apply a new value next call
_reapplyValue = false;
if (_isEnabled)
OnDataBindingEnabled(new DataBindingEventArgs(this));
else
OnDataBindingDisabled(new DataBindingEventArgs(this));
}
}
/// <inheritdoc />
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
if (!IsEnabled)
return;
// If Update() has not been called, reapply the previous value
if (_reapplyValue)
{
Converter.ApplyValue(_lastAppliedValue);
return;
}
TProperty converterValue = Converter.GetValue();
TProperty value = GetValue(converterValue);
Converter.ApplyValue(value);
_lastAppliedValue = value;
_reapplyValue = true;
_script.DataBindingExitNode.ApplyToDataBinding();
}
/// <inheritdoc />
@ -221,56 +194,18 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
#region Mode management
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Changes the data binding mode of the data binding to the specified <paramref name="dataBindingMode" />
/// </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;
}
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
ApplyDataBindingMode();
}
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
/// <summary>
/// Replaces the current data binding mode with one based on the provided data binding mode entity
/// </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();
}
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
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
@ -280,15 +215,16 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// General
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
if (registration != null)
ApplyRegistration(registration);
IsEnabled = Entity.IsEnabled;
}
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
DataBindingMode?.Load();
/// <inheritdoc />
public void LoadNodeScript()
{
_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 />
@ -297,40 +233,11 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
LayerProperty.Entity.DataBindingEntities.Add(Entity);
// Don't save an invalid state
if (Registration != null)
Entity.Identifier = Registration.DisplayName;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
DataBindingMode?.Save();
_script.Save();
Entity.IsEnabled = IsEnabled;
Entity.NodeScript = _script.Entity.Nodes.Any() ? _script.Entity : null;
}
#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
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using Artemis.Core.Modules;
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
/// <see cref="DataModel" />
/// </summary>
public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable
public interface IDataBinding : IStorageModel, IDisposable
{
/// <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>
/// <param name="delta">The delta to apply during update</param>
void UpdateWithDelta(TimeSpan delta);
ILayerProperty BaseLayerProperty { get; }
/// <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>
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;
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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