diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index a8b6dd96c..f73a999ab 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -84,4 +84,8 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 31cbebcb3..535046f29 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -1,5 +1,6 @@  True + True True True True @@ -43,12 +44,15 @@ True True True + True True True True True True True + True + True True True True @@ -83,4 +87,6 @@ True True True - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index c0b3e7ad7..751702129 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -77,13 +77,13 @@ namespace Artemis.Core internal static JsonSerializerSettings JsonConvertSettings = new() { - Converters = new List {new SKColorConverter(), new ForgivingIntConverter()} + Converters = new List {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()} }; internal static JsonSerializerSettings JsonConvertTypedSettings = new() { TypeNameHandling = TypeNameHandling.All, - Converters = new List {new SKColorConverter(), new ForgivingIntConverter()} + Converters = new List {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()} }; /// diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs deleted file mode 100644 index dd79f14cb..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumContainsConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class EnumContainsConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs deleted file mode 100644 index ef78b8470..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EnumNotContainsConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class EnumNotContainsConditionOperator : ConditionOperator - { - 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)); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs deleted file mode 100644 index 789f86d63..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class EqualsConditionOperator : ConditionOperator - { - public override string Description => "Equals"; - public override string Icon => "Equal"; - - public override bool Evaluate(object a, object b) - { - return Equals(a, b); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs deleted file mode 100644 index f9a898107..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class GreaterThanConditionOperator : ConditionOperator - { - public override string Description => "Is greater than"; - public override string Icon => "GreaterThan"; - - public override bool Evaluate(double a, double b) - { - return a > b; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs deleted file mode 100644 index f0a1bf2b9..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class GreaterThanOrEqualConditionOperator : ConditionOperator - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs deleted file mode 100644 index 720cd7c2c..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class LessThanConditionOperator : ConditionOperator - { - public override string Description => "Is less than"; - public override string Icon => "LessThan"; - - public override bool Evaluate(double a, double b) - { - return a < b; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs deleted file mode 100644 index 925a4afaa..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class LessThanOrEqualConditionOperator : ConditionOperator - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs deleted file mode 100644 index 4eeb8441a..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class NotEqualConditionOperator : ConditionOperator - { - public override string Description => "Does not equal"; - public override string Icon => "NotEqualVariant"; - - public override bool Evaluate(object a, object b) - { - return !Equals(a, b); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs deleted file mode 100644 index 0d8599df7..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class NotNullConditionOperator : ConditionOperator - { - public override string Description => "Is not null"; - public override string Icon => "CheckboxMarkedCircleOutline"; - - public override bool Evaluate(object a) - { - return a != null; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs deleted file mode 100644 index 46302a5ff..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class NullConditionOperator : ConditionOperator - { - public override string Description => "Is null"; - public override string Icon => "Null"; - - public override bool Evaluate(object a) - { - return a == null; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs deleted file mode 100644 index d32104262..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class NumberEqualsConditionOperator : ConditionOperator - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs deleted file mode 100644 index c0c29bb7a..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class NumberNotEqualConditionOperator : ConditionOperator - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs deleted file mode 100644 index 7df8eb9dc..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringContainsConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs deleted file mode 100644 index b8f5f91d0..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringEndsWithConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs deleted file mode 100644 index 970f96806..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringEqualsConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs deleted file mode 100644 index ada76f5f0..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Artemis.Core { - internal class StringMatchesRegexConditionOperator : ConditionOperator - { - 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); - } - } -} diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs deleted file mode 100644 index 1b683710b..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringNotContainsConditionOperator : ConditionOperator - { - 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)); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs deleted file mode 100644 index f51b63c4b..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringNotEqualConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs deleted file mode 100644 index 3b7c7ac7a..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class StringNotNullConditionOperator : ConditionOperator - { - public override string Description => "Is not null"; - public override string Icon => "CheckboxMarkedCircleOutline"; - - public override bool Evaluate(string a) - { - return !string.IsNullOrWhiteSpace(a); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs deleted file mode 100644 index 84d1f6775..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class StringNullConditionOperator : ConditionOperator - { - public override string Description => "Is null"; - public override string Icon => "Null"; - - public override bool Evaluate(string a) - { - return string.IsNullOrWhiteSpace(a); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs deleted file mode 100644 index d43b6af20..000000000 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class StringStartsWithConditionOperator : ConditionOperator - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs deleted file mode 100644 index 75818ac95..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - public class FloatDataBindingConverter : FloatDataBindingConverter - { - } - - /// - /// The type of layer property this converter is applied to - public class FloatDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public FloatDataBindingConverter() - { - SupportsSum = true; - SupportsInterpolate = true; - } - - /// - public override float Sum(float a, float b) - { - return a + b; - } - - /// - public override float Interpolate(float a, float b, double progress) - { - float diff = b - a; - return (float) (a + diff * progress); - } - - /// - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs deleted file mode 100644 index 584967a76..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a generic data binding converter that acts as the bridge between a - /// and a and does not support - /// sum or interpolation - /// - public class GeneralDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public GeneralDataBindingConverter() - { - SupportsSum = false; - SupportsInterpolate = false; - } - - /// - public override T Sum(T a, T b) - { - throw new NotSupportedException(); - } - - /// - public override T Interpolate(T a, T b, double progress) - { - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs deleted file mode 100644 index 795145630..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - public class IntDataBindingConverter : IntDataBindingConverter - { - } - - /// - public class IntDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public IntDataBindingConverter() - { - SupportsSum = true; - SupportsInterpolate = true; - } - - /// - /// Gets or sets the mode used for rounding during interpolation. Defaults to - /// - /// - public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero; - - /// - public override int Sum(int a, int b) - { - return a + b; - } - - /// - public override int Interpolate(int a, int b, double progress) - { - int diff = b - a; - return (int) Math.Round(a + diff * progress, InterpolationRoundingMode); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs deleted file mode 100644 index 6b708e088..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - /// - public class SKColorDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public SKColorDataBindingConverter() - { - SupportsInterpolate = true; - SupportsSum = true; - } - - /// - public override SKColor Sum(SKColor a, SKColor b) - { - return a.Sum(b); - } - - /// - public override SKColor Interpolate(SKColor a, SKColor b, double progress) - { - return a.Interpolate(b, (float) progress); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs deleted file mode 100644 index 32c4255ca..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs +++ /dev/null @@ -1,18 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorBrightenModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs deleted file mode 100644 index d26993e58..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs +++ /dev/null @@ -1,18 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorDarkenModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs deleted file mode 100644 index e3d370ebd..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorDesaturateModifierType : DataBindingModifierType - { - public override string Name => "Desaturate"; - public override string Icon => "ImageMinus"; - public override string Description => "Desaturates the color by the amount in percent"; - - public override SKColor Apply(SKColor currentValue, float parameterValue) - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs deleted file mode 100644 index bc46a053d..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs +++ /dev/null @@ -1,18 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorInvertModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs deleted file mode 100644 index 45781ce48..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs +++ /dev/null @@ -1,18 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorRotateHueModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs deleted file mode 100644 index 751f96c24..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorSaturateModifierType : DataBindingModifierType - { - public override string Name => "Saturate"; - public override string Icon => "ImagePlus"; - public override string Description => "Saturates the color by the amount in percent"; - - public override SKColor Apply(SKColor currentValue, float parameterValue) - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs deleted file mode 100644 index 9c45436df..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - internal class SKColorSumModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs deleted file mode 100644 index c58bfd3b8..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class AbsoluteModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs deleted file mode 100644 index 215952095..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Artemis.Core -{ - internal class DivideModifierType : DataBindingModifierType - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs deleted file mode 100644 index f8da7936e..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class MaxModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs deleted file mode 100644 index 5ab519e01..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class MinModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs deleted file mode 100644 index 8b710d3bf..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Artemis.Core -{ - internal class ModuloModifierType : DataBindingModifierType - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs deleted file mode 100644 index 3198777cc..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class MultiplicationModifierType : DataBindingModifierType - { - public override string Name => "Multiply by"; - public override string Icon => "Close"; - - public override double Apply(double currentValue, double parameterValue) - { - return currentValue * parameterValue; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs deleted file mode 100644 index 298d26e30..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Artemis.Core -{ - internal class PercentageOfModifierType : DataBindingModifierType - { - 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; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs deleted file mode 100644 index 2ea1a0709..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class PowerModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs deleted file mode 100644 index c4ca8f021..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class CeilingModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs deleted file mode 100644 index e26173049..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class FloorModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs deleted file mode 100644 index 3b3283396..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class RoundModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs deleted file mode 100644 index 72dfa3f55..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class SquareRootModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs deleted file mode 100644 index 0dcda285a..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class SubtractModifierType : DataBindingModifierType - { - public override string Name => "Subtract"; - public override string Icon => "Minus"; - - public override double Apply(double currentValue, double parameterValue) - { - return currentValue - parameterValue; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs deleted file mode 100644 index 3c2a816a6..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Artemis.Core -{ - internal class SumModifierType : DataBindingModifierType - { - public override string Name => "Sum"; - public override string Icon => "Plus"; - - public override double Apply(double currentValue, double parameterValue) - { - return currentValue + parameterValue; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs deleted file mode 100644 index bc79c9b79..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class CosecantModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs deleted file mode 100644 index 3b4090654..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class CosineModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs deleted file mode 100644 index 39e514aa3..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class CotangentModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs deleted file mode 100644 index c4790f2dd..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class SecantModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs deleted file mode 100644 index 5a315b316..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class SineModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs deleted file mode 100644 index 7a2b7c4ea..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Artemis.Core -{ - internal class TangentModifierType : DataBindingModifierType - { - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index 99a7b614a..ccaecf8b8 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -4,9 +4,14 @@ public class BoolLayerProperty : LayerProperty { internal BoolLayerProperty() + { + } + + /// + protected override void OnInitialize() { KeyframesSupported = false; - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter(), "Value"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index e811a4e0f..75d7c8a61 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -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 - { - public ColorStopDataBindingConverter() - { - SupportsInterpolate = true; - SupportsSum = true; - } - - /// - public override SKColor Sum(SKColor a, SKColor b) - { - return a.Sum(b); - } - - /// - public override SKColor Interpolate(SKColor a, SKColor b, double progress) - { - return a.Interpolate(b, (float) progress); - } - } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs index eb99a3b3e..ea7962b7a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs @@ -8,7 +8,6 @@ namespace Artemis.Core internal EnumLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index cb1a6eae8..2aec47126 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,12 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 34a3be646..d43dbf705 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,13 +5,17 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter(), "Start"); - RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter(), "End"); - CurrentValueSet += OnCurrentValueSet; DefaultValue = new FloatRange(); } + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End"); + } + /// protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index f69a1bdbc..03681df72 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,12 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 0b1942356..fea761b8e 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,13 +5,17 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter(), "Start"); - RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter(), "End"); - CurrentValueSet += OnCurrentValueSet; DefaultValue = new IntRange(); } + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End"); + } + /// protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { diff --git a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs index 9bbd77eac..c3341ee32 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs @@ -8,7 +8,6 @@ internal LayerBrushReferenceLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 46370568a..38caad133 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,12 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 3cd654ad9..df9517eeb 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,13 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter(), "X"); - RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter(), "Y"); + } + + /// + 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"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 3274c26f9..e8344e0c9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,13 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter(), "Width"); - RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter(), "Height"); + } + + /// + 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"); } /// diff --git a/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs b/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs new file mode 100644 index 000000000..c01cfae65 --- /dev/null +++ b/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Provides data for data binding events. + /// + public class DataBindingEventArgs : EventArgs + { + internal DataBindingEventArgs(IDataBinding dataBinding) + { + DataBinding = dataBinding; + } + + /// + /// Gets the data binding this event is related to + /// + public IDataBinding DataBinding { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs b/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs deleted file mode 100644 index fb81fa23f..000000000 --- a/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Artemis.Core -{ - internal class ConditionOperatorStoreEvent - { - public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration) - { - Registration = registration; - } - - public ConditionOperatorRegistration Registration { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs b/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs deleted file mode 100644 index c954a7f60..000000000 --- a/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Artemis.Core -{ - internal class DataBindingModifierTypeStoreEvent - { - public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration) - { - TypeRegistration = typeRegistration; - } - - public DataBindingModifierTypeRegistration TypeRegistration { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs b/src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs new file mode 100644 index 000000000..e91d27b6a --- /dev/null +++ b/src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class NodeTypeStoreEvent + { + public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration) + { + TypeRegistration = typeRegistration; + } + + public NodeTypeRegistration TypeRegistration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/IEnumerableExtensions.cs b/src/Artemis.Core/Extensions/IEnumerableExtensions.cs index 3fe2b8caa..c7ee21841 100644 --- a/src/Artemis.Core/Extensions/IEnumerableExtensions.cs +++ b/src/Artemis.Core/Extensions/IEnumerableExtensions.cs @@ -92,5 +92,25 @@ namespace Artemis.Core } } } + + /// + /// Returns the index of the provided element inside the read only collection + /// + /// The type of element to find + /// The collection to search in + /// The element to find + /// If found, the index of the element to find; otherwise -1 + public static int IndexOf(this IReadOnlyCollection self, T elementToFind) + { + int i = 0; + foreach (T element in self) + { + if (Equals(element, elementToFind)) + return i; + i++; + } + + return -1; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/SKColorExtensions.cs b/src/Artemis.Core/Extensions/SKColorExtensions.cs index 819e09045..f1e9297ec 100644 --- a/src/Artemis.Core/Extensions/SKColorExtensions.cs +++ b/src/Artemis.Core/Extensions/SKColorExtensions.cs @@ -57,6 +57,19 @@ namespace Artemis.Core ); } + /// + /// Darkens the color by the specified amount + /// + /// The color to darken + /// The brightness of the new color + /// The darkened color + 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); diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index 44f851c16..672c76e15 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -130,8 +130,11 @@ namespace Artemis.Core /// at all /// /// - 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()) diff --git a/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs b/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs new file mode 100644 index 000000000..e2ffd62a0 --- /dev/null +++ b/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs @@ -0,0 +1,25 @@ +using System; +using Newtonsoft.Json; + +namespace Artemis.Core.JsonConverters +{ + internal class NumericJsonConverter : JsonConverter + { + #region Overrides of JsonConverter + + /// + public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer) + { + float floatValue = value; + writer.WriteValue(floatValue); + } + + /// + public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return new Numeric(reader.Value); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs deleted file mode 100644 index 4214e14de..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a condition operator that performs a boolean operation - /// - /// To implement your own condition operator, inherit or - /// - /// - /// - public abstract class BaseConditionOperator - { - /// - /// Gets or sets the description of this logical operator - /// - public abstract string Description { get; } - - /// - /// Gets or sets the icon of this logical operator - /// - public abstract string Icon { get; } - - /// - /// Gets the plugin this condition operator belongs to - /// Note: Not set until after registering - /// - public Plugin? Plugin { get; internal set; } - - /// - /// Gets the left side type of this condition operator - /// - public abstract Type LeftSideType { get; } - - /// - /// Gets the right side type of this condition operator. May be null if the operator does not support a right side - /// - public abstract Type? RightSideType { get; } - - /// - /// Returns whether the given type is supported by the operator - /// - /// The type to check for, must be either the same or be castable to the target type - /// Which side of the operator to check, left or right - 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); - } - - /// - /// Evaluates the condition with the input types being provided as objects - /// - /// This leaves the caller responsible for the types matching and - /// - /// - /// - /// The left side value, type should match - /// The right side value, type should match - /// The result of the boolean condition's evaluation - internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue); - } - - /// - /// Represents a side of a condition parameter - /// - public enum ConditionParameterSide - { - /// - /// The left side of a condition parameter - /// - Left, - - /// - /// The right side of a condition parameter - /// - Right - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs deleted file mode 100644 index 2134cbaf9..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a condition operator that performs a boolean operation using a left- and right-side - /// - public abstract class ConditionOperator : BaseConditionOperator - { - /// - public override Type LeftSideType => typeof(TLeftSide); - - /// - public override Type RightSideType => typeof(TRightSide); - - /// - /// Evaluates the operator on a and b - /// - /// The parameter on the left side of the expression - /// The parameter on the right side of the expression - public abstract bool Evaluate(TLeftSide a, TRightSide b); - - /// - internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue) - { - // TODO: Can we avoid boxing/unboxing? - TLeftSide leftSide; - if (leftSideValue != null) - { - 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!); - } - } - - /// - /// Represents a condition operator that performs a boolean operation using only a left side - /// - public abstract class ConditionOperator : BaseConditionOperator - { - /// - public override Type LeftSideType => typeof(TLeftSide); - - /// - /// Always null, not applicable to this type of condition operator - /// - public override Type? RightSideType => null; - - /// - /// Evaluates the operator on a and b - /// - /// The parameter on the left side of the expression - public abstract bool Evaluate(TLeftSide a); - - /// - 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!); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs deleted file mode 100644 index ac8ac1510..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs +++ /dev/null @@ -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 -{ - /// - /// An abstract class for display condition parts - /// - public abstract class DataModelConditionPart : IDisposable - { - private readonly List _children = new(); - - protected DataModelConditionPart() - { - Children = new ReadOnlyCollection(_children); - } - - /// - /// Gets the parent of this part - /// - public DataModelConditionPart? Parent { get; internal set; } - - /// - /// Gets the children of this part - /// - public ReadOnlyCollection Children { get; } - - /// - /// Adds a child to the display condition part's collection - /// - /// - /// An optional index at which to insert the condition - 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(); - } - } - - /// - /// Removes a child from the display condition part's collection - /// - /// The child to remove - public void RemoveChild(DataModelConditionPart dataModelConditionPart) - { - if (_children.Contains(dataModelConditionPart)) - { - dataModelConditionPart.Parent = null; - _children.Remove(dataModelConditionPart); - OnChildRemoved(); - } - } - - /// - /// Removes all children. You monster. - /// - public void ClearChildren() - { - while (Children.Any()) - RemoveChild(Children[0]); - } - - /// - /// Evaluates the condition part on the data model - /// - /// - public abstract bool Evaluate(); - - /// - /// Evaluates the condition part on the given target (currently only for lists) - /// - /// - /// - internal abstract bool EvaluateObject(object? target); - - internal abstract void Save(); - internal abstract DataModelConditionPartEntity GetEntity(); - - #region IDisposable - - /// - /// Disposed the condition part - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Events - - /// - /// Occurs when a child-condition was added - /// - public event EventHandler? ChildAdded; - - /// - /// Occurs when a child-condition was removed - /// - public event EventHandler? ChildRemoved; - - /// - /// Invokers the event - /// - protected virtual void OnChildAdded() - { - ChildAdded?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invokers the event - /// - protected virtual void OnChildRemoved() - { - ChildRemoved?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs deleted file mode 100644 index f836d9157..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs +++ /dev/null @@ -1,411 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile.Abstract; -using Artemis.Storage.Entities.Profile.Conditions; -using Newtonsoft.Json; - -namespace Artemis.Core -{ - /// - /// A predicate in a data model condition using either two data model values or one data model value and a - /// static value - /// - public abstract class DataModelConditionPredicate : DataModelConditionPart - { - /// - /// Creates a new instance of the class - /// - /// - /// - /// A new empty entity - 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; - } - - /// - /// Gets or sets the predicate type - /// - public ProfileRightSideType PredicateType { get; set; } - - /// - /// Gets the operator - /// - public BaseConditionOperator? Operator { get; protected set; } - - /// - /// Gets the path of the left property - /// - public DataModelPath? LeftPath { get; protected set; } - - /// - /// Gets the path of the right property - /// - public DataModelPath? RightPath { get; protected set; } - - /// - /// Gets the right static value, only used it is - /// - /// - public object? RightStaticValue { get; protected set; } - - internal DataModelConditionPredicateEntity Entity { get; set; } - - /// - public override string ToString() - { - if (PredicateType == ProfileRightSideType.Dynamic) - return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}"; - return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}"; - } - - #region IDisposable - - /// - protected override void Dispose(bool disposing) - { - ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; - - LeftPath?.Dispose(); - RightPath?.Dispose(); - - base.Dispose(disposing); - } - - #endregion - - #region Initialization - - internal void Initialize() - { - ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; - - InitializeLeftPath(); - - // Operator - if (Entity.OperatorPluginGuid != null) - { - BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; - if (conditionOperator != null) - UpdateOperator(conditionOperator); - } - - // Right side dynamic - if (PredicateType == ProfileRightSideType.Dynamic) - InitializeRightPath(); - // Right side static - else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) - { - try - { - // If 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); - } - - /// - /// Initializes the left path of this condition predicate - /// - protected abstract void InitializeLeftPath(); - - /// - /// Initializes the right path of this condition predicate - /// - protected abstract void InitializeRightPath(); - - #endregion - - #region Modification - - /// - /// Updates the left side of the predicate - /// - /// The path pointing to the left side value inside the data model - public 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(); - } - - /// - /// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression - /// - /// The path pointing to the right side value inside the data model - public void UpdateRightSideDynamic(DataModelPath? path) - { - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path"); - if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right)) - throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}"); - - RightPath?.Dispose(); - RightPath = path != null ? new DataModelPath(path) : null; - - PredicateType = ProfileRightSideType.Dynamic; - } - - /// - /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression - /// - /// The right side value to use - public void UpdateRightSideStatic(object? staticValue) - { - PredicateType = ProfileRightSideType.Static; - RightPath?.Dispose(); - RightPath = null; - - // 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; - } - - /// - /// Updates the operator of the predicate and re-compiles the expression - /// - /// - 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(); - } - - /// - /// Determines the best type to use for the right side op this predicate - /// - 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 - - /// - public override bool Evaluate() - { - if (Operator == null || LeftPath == null || !LeftPath.IsValid) - return false; - - // If the operator does not support a right side, immediately evaluate with null - if (Operator.RightSideType == null) - return Operator.InternalEvaluate(LeftPath.GetValue(), null); - - // Compare with a static value - if (PredicateType == ProfileRightSideType.Static) - { - object? leftSideValue = LeftPath.GetValue(); - if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null) - return false; - - return Operator.InternalEvaluate(leftSideValue, RightStaticValue); - } - - if (RightPath == null || !RightPath.IsValid) - return false; - - // Compare with dynamic values - return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue()); - } - - /// - internal override bool EvaluateObject(object? target) - { - return false; - } - - #endregion - - #region Storage - - internal override DataModelConditionPartEntity GetEntity() - { - return Entity; - } - - internal override void Save() - { - // Don't save an invalid state - if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid) - return; - - Entity.PredicateType = (int) PredicateType; - - LeftPath?.Save(); - Entity.LeftPath = LeftPath?.Entity; - RightPath?.Save(); - Entity.RightPath = RightPath?.Entity; - - Entity.RightStaticValue = 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 - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs deleted file mode 100644 index 0ff661385..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System; -using System.Linq; -using Artemis.Storage.Entities.Profile.Abstract; -using Artemis.Storage.Entities.Profile.Conditions; - -namespace Artemis.Core -{ - /// - /// A condition that evaluates to true when an event is triggered - /// - public class DataModelConditionEvent : DataModelConditionPart - { - private bool _disposed; - private bool _reinitializing; - private IDataModelEvent? _valueChangedEvent; - - /// - /// Creates a new instance of the class - /// - /// - public DataModelConditionEvent(DataModelConditionPart parent) - { - Parent = parent; - Entity = new DataModelConditionEventEntity(); - - Initialize(); - } - - internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity) - { - Parent = parent; - Entity = entity; - - Initialize(); - } - - /// - /// Gets the path of the event property - /// - public DataModelPath? EventPath { get; private set; } - - /// - /// Gets the last time the event this condition is applied to was triggered - /// - public DateTime LastTrigger { get; private set; } - - /// - /// Gets or sets the type of argument the event provides - /// - public Type? EventArgumentType { get; private set; } - - internal DataModelConditionEventEntity Entity { get; set; } - - /// - 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; - } - - /// - /// Updates the event the condition is triggered by - /// - public void UpdateEvent(DataModelPath? path) - { - if (_disposed) - throw new ObjectDisposedException("DataModelConditionEvent"); - - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update event to an invalid path"); - - EventPath?.Dispose(); - EventPath = path != null ? new DataModelPath(path) : null; - SubscribeToEventPath(); - 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; - } - - /// - /// Returns the this is triggered by - /// - /// The this is triggered by - public IDataModelEvent? GetDataModelEvent() - { - if (_valueChangedEvent != null) - return _valueChangedEvent; - return EventPath?.GetValue() as IDataModelEvent; - } - - #region IDisposable - - /// - protected override void Dispose(bool disposing) - { - _disposed = true; - - EventPath?.Dispose(); - - foreach (DataModelConditionPart child in Children) - child.Dispose(); - - base.Dispose(disposing); - } - - #endregion - - internal override bool EvaluateObject(object? target) - { - return false; - } - - internal override void Save() - { - // Don't save an invalid state - if (EventPath != null && !EventPath.IsValid) - return; - - // Target list - EventPath?.Save(); - Entity.EventPath = EventPath?.Entity; - - // Children - Entity.Children.Clear(); - Entity.Children.AddRange(Children.Select(c => c.GetEntity())); - foreach (DataModelConditionPart child in Children) - child.Save(); - } - - internal override DataModelConditionPartEntity GetEntity() - { - return Entity; - } - - internal void Initialize() - { - ClearChildren(); - - if (Entity.EventPath == null) - return; - - 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 - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs deleted file mode 100644 index c6477ba65..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEventPredicate.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Entities.Profile.Conditions; - -namespace Artemis.Core -{ - /// - /// A predicate like evaluated inside a - /// - public class DataModelConditionEventPredicate : DataModelConditionPredicate - { - /// - /// Creates a new instance of the class - /// - /// - /// - public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) - : base(parent, predicateType, new DataModelConditionEventPredicateEntity()) - { - DataModelConditionEvent = null!; - ApplyParentEvent(); - Initialize(); - } - - internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity) - : base(parent, entity) - { - DataModelConditionEvent = null!; - ApplyParentEvent(); - Initialize(); - } - - /// - /// Gets the data model condition event this predicate belongs to - /// - public DataModelConditionEvent DataModelConditionEvent { get; private set; } - - private void ApplyParentEvent() - { - DataModelConditionPart? current = Parent; - while (current != null) - { - if (current is DataModelConditionEvent parentEvent) - { - DataModelConditionEvent = parentEvent; - return; - } - - current = current.Parent; - } - - if (DataModelConditionEvent == null) - throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event"); - } - - private object? GetEventPathValue(DataModelPath path, object? target) - { - lock (path) - { - if (!(path.Target is EventPredicateWrapperDataModel wrapper)) - throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target"); - - wrapper.UntypedArguments = target; - return path.GetValue(); - } - } - - #region Initialization - - /// - protected override void InitializeLeftPath() - { - if (Entity.LeftPath != null) - LeftPath = DataModelConditionEvent.EventArgumentType != null - ? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath) - : null; - } - - /// - protected override void InitializeRightPath() - { - if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) - { - // Right side dynamic using event arguments - if (Entity.RightPath.WrapperType == PathWrapperType.Event) - { - RightPath = DataModelConditionEvent.EventArgumentType != null - ? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath) - : null; - } - // Right side dynamic - else - RightPath = new DataModelPath(null, Entity.RightPath); - } - } - - #endregion - - #region Modification - - /// - public override Type? GetPreferredRightSideType() - { - Type? preferredType = Operator?.RightSideType; - Type? leftSideType = LeftPath?.GetPropertyType(); - if (preferredType == null) - return null; - - if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType)) - preferredType = leftSideType; - - return preferredType; - } - - #endregion - - #region Evaluation - - /// - /// Not supported for event predicates, always returns false - /// - public override bool Evaluate() - { - return false; - } - - internal override bool EvaluateObject(object? target) - { - if (Operator == null || LeftPath == null || !LeftPath.IsValid) - return false; - - // If the operator does not support a right side, immediately evaluate with null - if (Operator.RightSideType == null) - return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null); - - // Compare with a static value - if (PredicateType == ProfileRightSideType.Static) - { - object? leftSideValue = GetEventPathValue(LeftPath, target); - if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null) - return false; - - return Operator.InternalEvaluate(leftSideValue, RightStaticValue); - } - - if (RightPath == null || !RightPath.IsValid) - return false; - - // Compare with dynamic values - if (PredicateType == ProfileRightSideType.Dynamic) - { - // If the path targets a property inside the event, evaluate on the event path value instead of the right path value - if (RightPath.Target is EventPredicateWrapperDataModel) - return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target)); - return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue()); - } - - return false; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs deleted file mode 100644 index e65030a7a..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile.Conditions; - -namespace Artemis.Core -{ - /// - /// A predicate in a data model condition using either two data model values or one data model value and a - /// static value - /// - public class DataModelConditionGeneralPredicate : DataModelConditionPredicate - { - /// - /// Creates a new instance of the class - /// - /// - /// - public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) - : base(parent, predicateType, new DataModelConditionGeneralPredicateEntity()) - { - Initialize(); - } - - internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity) - : base(parent, entity) - { - Initialize(); - } - - #region Modification - - /// - public override Type? GetPreferredRightSideType() - { - Type? preferredType = Operator?.RightSideType; - Type? leftSideType = LeftPath?.GetPropertyType(); - if (preferredType == null) - return null; - - if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType)) - preferredType = leftSideType; - - return preferredType; - } - - #endregion - - #region Initialization - - /// - protected override void InitializeLeftPath() - { - if (Entity.LeftPath != null) - LeftPath = new DataModelPath(null, Entity.LeftPath); - } - - /// - protected override void InitializeRightPath() - { - if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) - RightPath = new DataModelPath(null, Entity.RightPath); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs deleted file mode 100644 index 53dcaa0bd..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs +++ /dev/null @@ -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 -{ - /// - /// A group containing zero to many s which it evaluates using a boolean specific - /// operator - /// - public class DataModelConditionGroup : DataModelConditionPart - { - private bool _disposed; - - /// - /// Creates a new instance of the class - /// - /// - public DataModelConditionGroup(DataModelConditionPart? parent) - { - Parent = parent; - Entity = new DataModelConditionGroupEntity(); - ChildAdded += OnChildrenChanged; - ChildRemoved += OnChildrenChanged; - } - - /// - /// Creates a new instance of the class - /// - /// - /// - 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; - } - - /// - /// Gets or sets the boolean operator of this group - /// - public BooleanOperator BooleanOperator { get; set; } - - /// - /// Gets whether this group contains any events - /// - public bool ContainsEvents { get; private set; } - - internal DataModelConditionGroupEntity Entity { get; set; } - - /// - 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 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 - - /// - protected override void Dispose(bool disposing) - { - _disposed = true; - foreach (DataModelConditionPart child in Children) - child.Dispose(); - - base.Dispose(disposing); - } - - #endregion - - /// - 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); - } - } - - /// - /// Represents a boolean operator - /// - public enum BooleanOperator - { - /// - /// All the conditions in the group should evaluate to true - /// - And, - - /// - /// Any of the conditions in the group should evaluate to true - /// - Or, - - /// - /// All the conditions in the group should evaluate to false - /// - AndNot, - - /// - /// Any of the conditions in the group should evaluate to false - /// - OrNot - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs deleted file mode 100644 index 9dd05a5e8..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs +++ /dev/null @@ -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 -{ - /// - /// A condition that evaluates one or more predicates inside a list - /// - public class DataModelConditionList : DataModelConditionPart - { - private bool _disposed; - private bool _reinitializing; - - /// - /// Creates a new instance of the class - /// - /// - 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(); - } - - /// - /// Gets or sets the list operator - /// - public ListOperator ListOperator { get; set; } - - /// - /// Gets the path of the list property - /// - public DataModelPath? ListPath { get; private set; } - - /// - /// Gets the type of the content of the list this predicate is evaluated on - /// - public Type? ListType { get; set; } - - /// - /// Gets whether the list contains primitives - /// - public bool IsPrimitiveList { get; set; } - - internal DataModelConditionListEntity Entity { get; set; } - - /// - public override bool Evaluate() - { - if (_disposed) - throw new ObjectDisposedException("DataModelConditionList"); - - if (ListPath == null || !ListPath.IsValid) - return false; - - return EvaluateObject(ListPath.GetValue()); - } - - /// - /// Updates the list the predicate is evaluated on - /// - /// The path pointing to the list inside the list - 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 - - /// - 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 objectList = enumerable.Cast(); - 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 - } - - /// - /// Represents a list operator - /// - public enum ListOperator - { - /// - /// Any of the list items should evaluate to true - /// - Any, - - /// - /// All of the list items should evaluate to true - /// - All, - - /// - /// None of the list items should evaluate to true - /// - None, - - /// - /// A specific amount of the list items should evaluate to true - /// - Count - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs deleted file mode 100644 index 5fdc3a8d8..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using Artemis.Core.Modules; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Entities.Profile.Conditions; - -namespace Artemis.Core -{ - /// - /// A predicate like evaluated inside a - /// - public class DataModelConditionListPredicate : DataModelConditionPredicate - { - /// - /// Creates a new instance of the class - /// - /// - /// - 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(); - } - - /// - /// Gets the data model condition list this predicate belongs to - /// - 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 - - /// - 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; - } - - /// - 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 - - /// - public override Type? GetPreferredRightSideType() - { - Type? preferredType = Operator?.RightSideType; - Type? leftSideType = DataModelConditionList.IsPrimitiveList - ? DataModelConditionList.ListType - : LeftPath?.GetPropertyType(); - if (preferredType == null) - return null; - - if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType)) - preferredType = leftSideType; - - return preferredType; - } - - #endregion - - #region Evaluation - - /// - /// Not supported for list predicates, always returns false - /// - 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 - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs new file mode 100644 index 000000000..0ba1f5a86 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -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 +{ + /// + /// Represents a condition that is based on a + /// + 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? _script; + + /// + /// Creates a new instance of the class + /// + 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(); + } + + /// + /// Gets the script that drives the event condition + /// + public NodeScript? Script + { + get => _script; + set => SetAndNotify(ref _script, value); + } + + /// + /// Gets or sets the path to the event that drives this event condition + /// + public DataModelPath? EventPath + { + get => _eventPath; + set => SetAndNotify(ref _eventPath, value); + } + + /// + /// Gets or sets how the condition behaves when events trigger before the timeline finishes + /// + public TimeLineEventOverlapMode EventOverlapMode + { + get => _eventOverlapMode; + set => SetAndNotify(ref _eventOverlapMode, value); + } + + /// + /// Updates the event node, applying the selected event + /// + 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); + } + + /// + /// Updates the with a new empty node script + /// + public void CreateEmptyNodeScript() + { + Script?.Dispose(); + Script = new NodeScript($"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; + } + + /// + public IConditionEntity Entity => _entity; + + /// + public ProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + if (EventOverlapMode == TimeLineEventOverlapMode.Toggle) + { + if (Evaluate()) + IsMet = !IsMet; + } + else + { + IsMet = Evaluate(); + } + } + + /// + 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 + } + + /// + public void Dispose() + { + Script?.Dispose(); + EventPath?.Dispose(); + } + + #region Storage + + /// + public void Load() + { + EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode; + if (_entity.Script != null) + Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile); + if (_entity.EventPath != null) + EventPath = new DataModelPath(_entity.EventPath); + } + + /// + public void Save() + { + _entity.EventOverlapMode = (int) EventOverlapMode; + Script?.Save(); + _entity.Script = Script?.Entity; + EventPath?.Save(); + _entity.EventPath = EventPath?.Entity; + } + + /// + public void LoadNodeScript() + { + if (Script == null) + return; + + Script.Load(); + UpdateEventNode(); + Script.LoadConnections(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs new file mode 100644 index 000000000..e62d21617 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs @@ -0,0 +1,51 @@ +using System; +using Artemis.Storage.Entities.Profile.Abstract; + +namespace Artemis.Core +{ + /// + /// Represents a condition applied to a + /// + public interface ICondition : IDisposable, IStorageModel + { + /// + /// Gets the entity used to store this condition + /// + public IConditionEntity Entity { get; } + + /// + /// Gets the profile element this condition applies to + /// + public ProfileElement ProfileElement { get; } + + /// + /// Gets a boolean indicating whether the condition is currently met + /// + + bool IsMet { get; } + + /// + /// Updates the condition + /// + void Update(); + + /// + /// Applies the display condition to the provided timeline + /// + /// + /// + /// The timeline to apply the display condition to + void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline); + } + + /// + /// Represents a condition applied to a using a + /// + public interface INodeScriptCondition : ICondition + { + /// + /// Loads the node script this node script condition uses + /// + void LoadNodeScript(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs new file mode 100644 index 000000000..2419d1387 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs @@ -0,0 +1,102 @@ +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Core +{ + /// + /// Represents a condition that is based on a data model value + /// + public class StaticCondition : CorePropertyChanged, INodeScriptCondition + { + private readonly string _displayName; + private readonly StaticConditionEntity _entity; + + /// + /// Creates a new instance of the class + /// + public StaticCondition(ProfileElement profileElement) + { + _entity = new StaticConditionEntity(); + _displayName = profileElement.GetType().Name; + + ProfileElement = profileElement; + Script = new NodeScript($"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(); + } + + /// + /// Gets the script that drives the static condition + /// + public NodeScript Script { get; private set; } + + /// + public IConditionEntity Entity => _entity; + + /// + public ProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + if (!Script.ExitNodeConnected) + { + IsMet = true; + return; + } + + Script.Run(); + IsMet = Script.Result; + } + + /// + 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(); + } + + /// + public void Dispose() + { + Script.Dispose(); + } + + #region Storage + + /// + public void Load() + { + Script = new NodeScript($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile); + } + + /// + public void Save() + { + Script.Save(); + _entity.Script = Script.Entity; + } + + /// + public void LoadNodeScript() + { + Script.Load(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs deleted file mode 100644 index 7b30c20f9..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Artemis.Core.Modules; - -namespace Artemis.Core -{ - internal class EventPredicateWrapperDataModel : EventPredicateWrapperDataModel - { - [DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")] - public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!; - } - - /// - /// Represents a datamodel that wraps the event arguments of an event - /// - public abstract class EventPredicateWrapperDataModel : DataModel - { - internal EventPredicateWrapperDataModel() - { - Module = Constants.CorePluginFeature; - } - - /// - /// Gets the last arguments of this event as an object - /// - [DataModelIgnore] - public object? UntypedArguments { get; internal set; } - - /// - /// Creates a new instance of the class - /// - public static EventPredicateWrapperDataModel Create(Type type) - { - object? instance = Activator.CreateInstance(typeof(EventPredicateWrapperDataModel<>).MakeGenericType(type)); - if (instance == null) - throw new ArtemisCoreException($"Failed to create an instance of EventPredicateWrapperDataModel for type {type.Name}"); - - return (EventPredicateWrapperDataModel) instance; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs deleted file mode 100644 index 69a2fe087..000000000 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Reflection; -using Artemis.Core.Modules; - -namespace Artemis.Core -{ - internal class ListPredicateWrapperDataModel : ListPredicateWrapperDataModel - { - public T Value => (UntypedValue is T typedValue ? typedValue : default)!; - } - - /// - /// Represents a datamodel that wraps a value in a list - /// - public abstract class ListPredicateWrapperDataModel : DataModel - { - internal ListPredicateWrapperDataModel() - { - Module = Constants.CorePluginFeature; - } - - /// - /// Gets or sets the value of this list as an object - /// - [DataModelIgnore] - public object? UntypedValue { get; set; } - - /// - /// Gets or sets the name of the list item - /// - [DataModelIgnore] - public string? ItemName { get; set; } - - #region Overrides of DataModel - - /// - public override DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo) - { - if (!string.IsNullOrWhiteSpace(ItemName)) - return new DataModelPropertyAttribute {Name = ItemName}; - return base.GetPropertyDescription(propertyInfo); - } - - #endregion - - /// - /// Creates a new instance of the class - /// - 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 for type {type.Name}"); - - instance.ItemName = name; - return instance; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 6b560ee52..daeb72650 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -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 { /// - public class DataBinding : IDataBinding + public class DataBinding : IDataBinding { - private TProperty _currentValue = default!; + private readonly List _properties = new(); private bool _disposed; - private TimeSpan _easingProgress; - private TProperty _lastAppliedValue = default!; - private TProperty _previousValue = default!; - private bool _reapplyValue; + private bool _isEnabled; + private DataBindingNodeScript _script; - internal DataBinding(DataBindingRegistration dataBindingRegistration) + internal DataBinding(LayerProperty layerProperty) { - LayerProperty = dataBindingRegistration.LayerProperty; - Entity = new DataBindingEntity(); + LayerProperty = layerProperty; + + Entity = new DataBindingEntity(); + _script = new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); - ApplyRegistration(dataBindingRegistration); Save(); - ApplyDataBindingMode(); } internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) { LayerProperty = layerProperty; + Entity = entity; + _script = new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); // Load will add children so be initialized before that Load(); - ApplyDataBindingMode(); } - /// - /// Gets the data binding registration this data binding is based upon - /// - public DataBindingRegistration? Registration { get; private set; } - /// /// Gets the layer property this data binding targets /// public LayerProperty LayerProperty { get; } /// - /// Gets the converter used to apply this data binding to the + /// Gets the script used to populate the data binding /// - public DataBindingConverter? Converter { get; private set; } - - /// - /// Gets the data binding mode - /// - public IDataBindingMode? DataBindingMode { get; private set; } - - /// - /// Gets or sets the easing time of the data binding - /// - public TimeSpan EasingTime { get; set; } - - /// - /// Gets ors ets the easing function of the data binding - /// - public Easings.Functions EasingFunction { get; set; } + public INodeScript Script => _script; /// /// Gets the data binding entity this data binding uses for persistent storage @@ -69,38 +51,52 @@ namespace Artemis.Core public DataBindingEntity Entity { get; } /// - /// Gets the current value of the data binding + /// Updates the pending values of this data binding /// - /// The base value of the property the data binding is applied to - /// - 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(); } /// - /// 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 /// - public Type? GetTargetType() + /// The type of the layer property + /// The function to call to get the value of the property + /// The action to call to set the value of the property + /// The display name of the data binding property + public DataBindingProperty RegisterDataBindingProperty(Func getter, Action 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 property = new(getter, setter, displayName); + _properties.Add(property); + + OnDataBindingPropertyRegistered(); + return property; + } + + /// + /// Removes all data binding properties so they are no longer available to the data binding system + /// + public void ClearDataBindingProperties() + { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + + _properties.Clear(); + OnDataBindingPropertiesCleared(); } /// @@ -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 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)); - } - /// - /// Updates the smoothing progress of the data binding + /// Invokes the event /// - /// The timeline to apply during update - 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); + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertiesCleared() + { + DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingEnabled(DataBindingEventArgs e) + { + DataBindingEnabled?.Invoke(this, e); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingDisabled(DataBindingEventArgs e) + { + DataBindingDisabled?.Invoke(this, e); + } + + private string GetScriptName() + { + return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path; } /// - public void UpdateWithDelta(TimeSpan delta) + public ILayerProperty BaseLayerProperty => LayerProperty; + + /// + 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)); + } } + /// + public ReadOnlyCollection Properties => _properties.AsReadOnly(); + /// 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(); } /// @@ -221,56 +194,18 @@ namespace Artemis.Core GC.SuppressFinalize(this); } - #region Mode management + /// + public event EventHandler? DataBindingPropertyRegistered; - /// - /// Changes the data binding mode of the data binding to the specified - /// - 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; - } + /// + public event EventHandler? DataBindingPropertiesCleared; - ApplyDataBindingMode(); - } + /// + public event EventHandler? DataBindingEnabled; - /// - /// Replaces the current data binding mode with one based on the provided data binding mode entity - /// - /// The data binding mode entity to base the new data binding mode upon - public void ApplyDataBindingEntity(IDataBindingModeEntity dataBindingModeEntity) - { - Entity.DataBindingMode = dataBindingModeEntity; - ApplyDataBindingMode(); - } + /// + public event EventHandler? DataBindingDisabled; - private void ApplyDataBindingMode() - { - DataBindingMode?.Dispose(); - DataBindingMode = null; - - switch (Entity.DataBindingMode) - { - case DirectDataBindingEntity directDataBindingEntity: - DataBindingMode = new DirectDataBinding(this, directDataBindingEntity); - break; - case ConditionalDataBindingEntity conditionalDataBindingEntity: - DataBindingMode = new ConditionalDataBinding(this, conditionalDataBindingEntity); - break; - } - } - - #endregion #region Storage @@ -280,15 +215,16 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBinding"); - // General - DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.Identifier); - if (registration != null) - ApplyRegistration(registration); + IsEnabled = Entity.IsEnabled; + } - EasingTime = Entity.EasingTime; - EasingFunction = (Easings.Functions) Entity.EasingFunction; - - DataBindingMode?.Load(); + /// + public void LoadNodeScript() + { + _script.Dispose(); + _script = Entity.NodeScript != null + ? new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile) + : new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); } /// @@ -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 } - - /// - /// A mode that determines how the data binding is applied to the layer property - /// - public enum DataBindingModeType - { - /// - /// Disables the data binding - /// - None, - - /// - /// Replaces the layer property value with the data binding value - /// - Direct, - - /// - /// Replaces the layer property value with the data binding value - /// - Conditional - } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs deleted file mode 100644 index 2b3d7eb5a..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a data binding converter that acts as the bridge between a - /// and a - /// - public abstract class DataBindingConverter : IDataBindingConverter - { - /// - /// Gets the data binding this converter is applied to - /// - public DataBinding? DataBinding { get; private set; } - - /// - /// Gets whether or not this data binding converter supports the method - /// - public bool SupportsSum { get; protected set; } - - /// - /// Gets whether or not this data binding converter supports the method - /// - public bool SupportsInterpolate { get; protected set; } - - /// - /// Returns the sum of and - /// - public abstract TProperty Sum(TProperty a, TProperty b); - - /// - /// Returns the the interpolated value between and on a scale (generally) - /// between 0.0 and 1.0 defined by the - /// Note: The progress may go be negative or go beyond 1.0 depending on the easing method used - /// - /// The value to interpolate away from - /// The value to interpolate towards - /// The progress of the interpolation between 0.0 and 1.0 - /// - public abstract TProperty Interpolate(TProperty a, TProperty b, double progress); - - /// - /// Applies the to the layer property - /// - /// - public virtual void ApplyValue(TProperty value) - { - if (DataBinding?.Registration == null) - throw new ArtemisCoreException("Data binding converter is not yet initialized"); - DataBinding.Registration.Setter(value); - } - - /// - /// Returns the current base value of the data binding - /// - public virtual TProperty GetValue() - { - if (DataBinding?.Registration == null) - throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return DataBinding.Registration.Getter(); - } - - /// - /// Converts the provided object to a type of - /// - public virtual TProperty ConvertFromObject(object? source) - { - return (TProperty) Convert.ChangeType(source, typeof(TProperty))!; - } - - /// - /// Called when the data binding converter has been initialized and the is available - /// - protected virtual void OnInitialized() - { - } - - internal void Initialize(DataBinding dataBinding) - { - if (dataBinding.Registration == null) - throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); - - DataBinding = dataBinding; - OnInitialized(); - } - - /// - public Type SupportedType => typeof(TProperty); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs new file mode 100644 index 000000000..6c67300a2 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs @@ -0,0 +1,46 @@ +using System; + +namespace Artemis.Core +{ + /// + public class DataBindingProperty : IDataBindingProperty + { + internal DataBindingProperty(Func getter, Action setter, string displayName) + { + Getter = getter ?? throw new ArgumentNullException(nameof(getter)); + Setter = setter ?? throw new ArgumentNullException(nameof(setter)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + } + + /// + /// Gets the function to call to get the value of the property + /// + public Func Getter { get; } + + /// + /// Gets the action to call to set the value of the property + /// + public Action Setter { get; } + + /// + public string DisplayName { get; } + + /// + public Type ValueType => typeof(TProperty); + + /// + public object? GetValue() + { + return Getter(); + } + + /// + 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)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs deleted file mode 100644 index ab50c44c9..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Linq; -using Artemis.Storage.Entities.Profile.DataBindings; - -namespace Artemis.Core -{ - /// - public class DataBindingRegistration : IDataBindingRegistration - { - internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, - Func getter, Action 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)); - } - - /// - /// Gets the layer property this registration was made on - /// - public LayerProperty LayerProperty { get; } - - /// - /// Gets the converter that's used by the data binding - /// - public DataBindingConverter Converter { get; } - - /// - /// Gets the function to call to get the value of the property - /// - public Func Getter { get; } - - /// - /// Gets the action to call to set the value of the property - /// - public Action Setter { get; } - - /// - public string DisplayName { get; } - - /// - /// Gets the data binding created using this registration - /// - public DataBinding? DataBinding { get; internal set; } - - /// - public IDataBinding? GetDataBinding() - { - return DataBinding; - } - - /// - 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(LayerProperty, dataBinding); - return DataBinding; - } - - /// - 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); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs index 1f54ca146..e706a3701 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -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 to a value inside a /// /// - public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable + public interface IDataBinding : IStorageModel, IDisposable { /// - /// Updates the smoothing progress of the data binding and recalculates the value next call + /// Gets the layer property the data binding is applied to /// - /// The delta to apply during update - void UpdateWithDelta(TimeSpan delta); + ILayerProperty BaseLayerProperty { get; } /// - /// Applies the data binding to the layer property + /// Gets a list of sub-properties this data binding applies to + /// + ReadOnlyCollection Properties { get; } + + /// + /// Gets a boolean indicating whether the data binding is enabled or not + /// + bool IsEnabled { get; set; } + + /// + /// Applies the pending value of the data binding to the property /// void Apply(); + + /// + /// If the data binding is enabled, loads the node script for that data binding + /// + void LoadNodeScript(); + + /// + /// Occurs when a data binding property has been added + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + /// Occurs when all data binding properties have been removed + /// + public event EventHandler? DataBindingPropertiesCleared; + + /// + /// Occurs when a data binding has been enabled + /// + public event EventHandler? DataBindingEnabled; + + /// + /// Occurs when a data binding has been disabled + /// + public event EventHandler? DataBindingDisabled; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs deleted file mode 100644 index 9f207133f..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a data binding converter that acts as the bridge between a - /// and a - /// - public interface IDataBindingConverter - { - /// - /// Gets the type this converter supports - /// - public Type SupportedType { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs new file mode 100644 index 000000000..c6f299766 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs @@ -0,0 +1,32 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents a data binding registration + /// + public interface IDataBindingProperty + { + /// + /// Gets or sets the display name of the data binding registration + /// + string DisplayName { get; } + + /// + /// Gets the type of the value this data binding registration points to + /// + Type ValueType { get; } + + /// + /// Gets the value of the property this registration points to + /// + /// A value matching the type of + object? GetValue(); + + /// + /// Sets the value of the property this registration points to + /// + /// A value matching the type of + void SetValue(object? value); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs deleted file mode 100644 index c1321d4c3..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Artemis.Core -{ - /// - /// Represents a data binding registration - /// - public interface IDataBindingRegistration - { - /// - /// Gets or sets the display name of the data binding registration - /// - string DisplayName { get; } - - /// - /// Returns the data binding applied using this registration - /// - public IDataBinding? GetDataBinding(); - - /// - /// If found, creates a data binding from storage - /// - /// - IDataBinding? CreateDataBinding(); - - /// - /// If present, removes the current data binding - /// - void ClearDataBinding(); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs deleted file mode 100644 index 44261ef00..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/ConditionalDataBinding.cs +++ /dev/null @@ -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 -{ - /// - /// Represents a data binding mode that applies a value depending on conditions - /// - public class ConditionalDataBinding : IDataBindingMode - { - private readonly List> _conditions = new(); - private bool _disposed; - - internal ConditionalDataBinding(DataBinding dataBinding, ConditionalDataBindingEntity entity) - { - DataBinding = dataBinding; - Entity = entity; - Conditions = new ReadOnlyCollection>(_conditions); - Load(); - } - - /// - /// Gets a list of conditions applied to this data binding - /// - public ReadOnlyCollection> Conditions { get; } - - internal ConditionalDataBindingEntity Entity { get; } - - /// - public DataBinding DataBinding { get; } - - /// - public TProperty GetValue(TProperty baseValue) - { - if (_disposed) - throw new ObjectDisposedException("ConditionalDataBinding"); - - DataBindingCondition? condition = Conditions.FirstOrDefault(c => c.Evaluate()); - return condition == null ? baseValue : condition.Value; - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _disposed = true; - - foreach (DataBindingCondition dataBindingCondition in Conditions) - dataBindingCondition.Dispose(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Values - - /// - /// Adds a condition to the conditional data binding's collection - /// - /// The newly created - public DataBindingCondition AddCondition() - { - if (_disposed) - throw new ObjectDisposedException("ConditionalDataBinding"); - - DataBindingCondition condition = new(this); - _conditions.Add(condition); - - ApplyOrder(); - OnConditionsUpdated(); - - return condition; - } - - /// - /// Removes a condition from the conditional data binding's collection and disposes it - /// - /// - public void RemoveCondition(DataBindingCondition condition) - { - if (_disposed) - throw new ObjectDisposedException("ConditionalDataBinding"); - if (!_conditions.Contains(condition)) - return; - - _conditions.Remove(condition); - condition.Dispose(); - - ApplyOrder(); - OnConditionsUpdated(); - } - - /// - /// Applies the current order of conditions to the collection - /// - 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 condition = _conditions[index]; - condition.Order = index + 1; - } - } - - #endregion - - #region Storage - - /// - public void Load() - { - foreach (DataBindingConditionEntity dataBindingConditionEntity in Entity.Values) - _conditions.Add(new DataBindingCondition(this, dataBindingConditionEntity)); - - ApplyOrder(); - } - - /// - public void Save() - { - Entity.Values.Clear(); - foreach (DataBindingCondition dataBindingCondition in Conditions) - dataBindingCondition.Save(); - } - - #endregion - - #region Events - - /// - /// Occurs when a condition is added or removed - /// - public event EventHandler? ConditionsUpdated; - - /// - /// Invokes the event - /// - protected virtual void OnConditionsUpdated() - { - ConditionsUpdated?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/DataBindingCondition.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/DataBindingCondition.cs deleted file mode 100644 index daa22c4fa..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/DataBindingCondition.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile.DataBindings; - -namespace Artemis.Core -{ - /// - public class DataBindingCondition : IDataBindingCondition - { - private bool _disposed; - - /// - /// Creates a new instance of the class - /// - /// The conditional data binding this condition is applied too - internal DataBindingCondition(ConditionalDataBinding 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 conditionalDataBinding, DataBindingConditionEntity entity) - { - ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding)); - Entity = entity; - Condition = null!; - Value = default!; - - Load(); - } - - /// - /// Gets the conditional data binding this condition is applied to - /// - public ConditionalDataBinding ConditionalDataBinding { get; } - - /// - /// Gets or sets the position at which the modifier appears on the data binding - /// - public int Order { get; set; } - - /// - /// Gets or sets the value to be applied when the condition is met - /// - public TProperty Value { get; set; } - - /// - /// Gets the root group of the condition that must be met - /// - public DataModelConditionGroup Condition { get; private set; } - - internal DataBindingConditionEntity Entity { get; set; } - - /// - public bool Evaluate() - { - return Condition.Evaluate(); - } - - /// - 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; - } - - /// - 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(Entity.Value))!; - Order = Entity.Order; - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _disposed = true; - Condition.Dispose(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/IDataBindingCondition.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/IDataBindingCondition.cs deleted file mode 100644 index 26ad9a9f2..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Conditional/IDataBindingCondition.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a condition and a value inside a - /// - public interface IDataBindingCondition : IStorageModel, IDisposable - { - /// - /// Evaluates the condition - /// - bool Evaluate(); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs deleted file mode 100644 index 2d85efb96..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// A modifier that changes the source value of a data binding in some way - /// - /// To implement your own condition operator, inherit or - /// - /// - /// - public abstract class BaseDataBindingModifierType - { - /// - /// Gets the plugin this data binding modifier belongs to - /// Note: Not set until after registering - /// - public Plugin? Plugin { get; internal set; } - - /// - /// Gets the value type of this modifier type - /// - public abstract Type ValueType { get; } - - /// - /// Gets the parameter type of this modifier type. May be null if the modifier type does not support a parameter - /// - public abstract Type? ParameterType { get; } - - /// - /// Gets the name of this modifier - /// - public abstract string Name { get; } - - /// - /// Gets or sets the icon of this modifier - /// - public abstract string? Icon { get; } - - /// - /// Gets the description of this modifier - /// - public virtual string? Description => null; - - /// - /// Gets the category of this modifier - /// - public virtual string? Category => null; - - /// - /// Returns whether the given type is supported by the modifier - /// - /// The type to check for, must be either the same or be castable to the target type - /// Which part of the modifier to check, the value or the parameter - public bool SupportsType(Type type, ModifierTypePart part) - { - if (type == null) - return true; - if (part == ModifierTypePart.Value) - return ValueType.IsCastableFrom(type); - return ParameterType != null && ParameterType.IsCastableFrom(type); - } - - /// - /// Applies the modifier to the provided current value - /// - /// This leaves the caller responsible for the types matching and - /// - /// - /// - /// The current value before modification, type should match - /// - /// The parameter to use for the modification, type should match - /// - /// The modified value, with a type of - internal abstract object? InternalApply(object? currentValue, object? parameterValue); - } - - /// - /// Represents a part of a modifier type - /// - public enum ModifierTypePart - { - /// - /// The value part of a modifier, backed by - /// - Value, - - /// - /// The parameter part of a modifier, backed by - /// - Parameter - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs deleted file mode 100644 index 1b11249c9..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile.DataBindings; -using Newtonsoft.Json; - -namespace Artemis.Core -{ - /// - public class DataBindingModifier : IDataBindingModifier - { - private bool _disposed; - - internal DataBindingModifier(DirectDataBinding directDataBinding, ProfileRightSideType parameterType) - { - DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding)); - Order = directDataBinding.Modifiers.Count + 1; - ParameterType = parameterType; - Entity = new DataBindingModifierEntity(); - Initialize(); - Save(); - } - - internal DataBindingModifier(DirectDataBinding directDataBinding, DataBindingModifierEntity entity) - { - DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding)); - Entity = entity; - Load(); - Initialize(); - } - - /// - /// Gets the type of modifier that is being applied - /// - public BaseDataBindingModifierType? ModifierType { get; private set; } - - /// - /// Gets the direct data binding this modifier is applied to - /// - public DirectDataBinding DirectDataBinding { get; } - - /// - /// Gets the type of the parameter, can either be dynamic (based on a data model value) or static - /// - public ProfileRightSideType ParameterType { get; set; } - - /// - /// Gets or sets the position at which the modifier appears on the data binding - /// - public int Order { get; set; } - - /// - /// Gets the path of the parameter property - /// - public DataModelPath? ParameterPath { get; set; } - - /// - /// Gets the parameter static value, only used it is - /// - /// - public object? ParameterStaticValue { get; private set; } - - internal DataBindingModifierEntity Entity { get; set; } - - /// - /// Applies the modifier to the provided value - /// - /// The value to apply the modifier to, should be of the same type as the data binding target - /// The modified value - public object? Apply(object? currentValue) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - if (ModifierType == null) - return currentValue; - - if (ModifierType.ParameterType == null) - return ModifierType.InternalApply(currentValue, null); - - if (ParameterType == ProfileRightSideType.Dynamic && ParameterPath != null && ParameterPath.IsValid) - { - object? value = ParameterPath.GetValue(); - return ModifierType.InternalApply(currentValue, value); - } - - if (ParameterType == ProfileRightSideType.Static) - return ModifierType.InternalApply(currentValue, ParameterStaticValue); - - return currentValue; - } - - /// - /// Updates the modifier type of the modifier and re-compiles the expression - /// - /// - public void UpdateModifierType(BaseDataBindingModifierType? modifierType) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - if (modifierType == null) - { - ModifierType = null; - return; - } - - Type? targetType = DirectDataBinding.DataBinding.GetTargetType(); - if (targetType != null && !modifierType.SupportsType(targetType, ModifierTypePart.Value)) - throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + - $"it does not support this data binding's type {targetType.Name}"); - - ModifierType = modifierType; - ValidateParameter(); - } - - /// - /// Updates the parameter of the modifier and makes the modifier dynamic - /// - /// The path pointing to the parameter - public void UpdateParameterDynamic(DataModelPath? path) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update parameter of data binding modifier to an invalid path"); - - ParameterPath?.Dispose(); - ParameterPath = path != null ? new DataModelPath(path) : null; - - ParameterType = ProfileRightSideType.Dynamic; - } - - /// - /// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression - /// - /// The static value to use as a parameter - public void UpdateParameterStatic(object? staticValue) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - ParameterType = ProfileRightSideType.Static; - ParameterPath?.Dispose(); - ParameterPath = null; - - Type? parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); - - // If not null ensure the types match and if not, convert it - if (parameterType == null || staticValue != null && staticValue.GetType() == parameterType) - ParameterStaticValue = staticValue; - else if (staticValue != null) - ParameterStaticValue = Convert.ChangeType(staticValue, parameterType); - // If null create a default instance for value types or simply make it null for reference types - else if (parameterType.IsValueType) - ParameterStaticValue = Activator.CreateInstance(parameterType); - else - ParameterStaticValue = null; - } - - private void ValidateParameter() - { - if (ModifierType == null) - return; - - if (ParameterType == ProfileRightSideType.Dynamic) - { - if (ParameterPath == null || !ParameterPath.IsValid) - return; - - Type parameterType = ParameterPath.GetPropertyType()!; - if (!ModifierType.SupportsType(parameterType, ModifierTypePart.Parameter)) - UpdateParameterDynamic(null); - } - else - { - if (ParameterStaticValue == null) - return; - - if (!ModifierType.SupportsType(ParameterStaticValue.GetType(), ModifierTypePart.Parameter)) - UpdateParameterStatic(null); - } - } - - private void Initialize() - { - DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded; - DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved; - - // Modifier type - if (Entity.ModifierTypePluginGuid != null && ModifierType == null) - { - BaseDataBindingModifierType? modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType; - if (modifierType != null) - UpdateModifierType(modifierType); - } - - // Dynamic parameter - if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterPath != null) - { - ParameterPath = new DataModelPath(null, Entity.ParameterPath); - } - // Static parameter - else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null) - { - // Use the target type so JSON.NET has a better idea what to do - Type? parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); - object? staticValue = null; - - try - { - staticValue = parameterType != null - ? CoreJson.DeserializeObject(Entity.ParameterStaticValue, parameterType) - : CoreJson.DeserializeObject(Entity.ParameterStaticValue); - } - // If deserialization fails, use the type's default - catch (JsonSerializationException e) - { - DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e); - if (parameterType != null) - staticValue = Activator.CreateInstance(parameterType); - } - - UpdateParameterStatic(staticValue); - } - } - - /// - public void Save() - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - // Don't save an invalid state - if (ParameterPath != null && !ParameterPath.IsValid) - return; - - if (!DirectDataBinding.Entity.Modifiers.Contains(Entity)) - DirectDataBinding.Entity.Modifiers.Add(Entity); - - // Modifier - if (ModifierType?.Plugin != null) - { - Entity.ModifierType = ModifierType.GetType().Name; - Entity.ModifierTypePluginGuid = ModifierType.Plugin.Guid; - } - - // General - Entity.Order = Order; - Entity.ParameterType = (int) ParameterType; - - // Parameter - ParameterPath?.Save(); - Entity.ParameterPath = ParameterPath?.Entity; - - Entity.ParameterStaticValue = CoreJson.SerializeObject(ParameterStaticValue); - } - - /// - public void Load() - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - // Modifier type is done during Initialize - - // General - Order = Entity.Order; - ParameterType = (ProfileRightSideType) Entity.ParameterType; - - // Parameter is done during initialize - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _disposed = true; - - DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded; - DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved; - - ParameterPath?.Dispose(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Event handlers - - private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object? sender, DataBindingModifierTypeStoreEvent e) - { - if (ModifierType != null) - return; - - BaseDataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType; - if (modifierType.Plugin!.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType) - UpdateModifierType(modifierType); - } - - private void DataBindingModifierTypeStoreOnDataBindingModifierRemoved(object? sender, DataBindingModifierTypeStoreEvent e) - { - if (e.TypeRegistration.DataBindingModifierType == ModifierType) - UpdateModifierType(null); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifierType.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifierType.cs deleted file mode 100644 index c09cd8fcc..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifierType.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// A modifier that changes the source value of a data binding in some way using a parameter - /// - public abstract class DataBindingModifierType : BaseDataBindingModifierType - { - /// - public override Type ValueType => typeof(TValue); - - /// - public override Type ParameterType => typeof(TParameter); - - /// - /// Called whenever the modifier must apply to a specific value - /// - /// - /// The current value before modification - /// - /// - /// The parameter to use for the modification - /// - /// The modified value> - public abstract TValue Apply(TValue currentValue, TParameter parameterValue); - - /// - internal override object? InternalApply(object? currentValue, object? parameterValue) - { - // TODO: Can we avoid boxing/unboxing? - TValue current; - if (currentValue != null) - current = (TValue) Convert.ChangeType(currentValue, typeof(TValue)); - else - current = default; - - TParameter parameter; - if (parameterValue != null) - parameter = (TParameter) Convert.ChangeType(parameterValue, typeof(TParameter)); - else - parameter = default; - - return Apply(current!, parameter!); - } - } - - /// - /// A modifier that changes the source value of a data binding in some way - /// - public abstract class DataBindingModifierType : BaseDataBindingModifierType - { - /// - public override Type ValueType => typeof(TValue); - - /// - public override Type? ParameterType => null; - - /// - /// Called whenever the modifier must apply to a specific value - /// - /// - /// The current value before modification - /// - /// The modified value - public abstract TValue Apply(TValue currentValue); - - /// - internal override object? InternalApply(object? currentValue, object? parameterValue) - { - // TODO: Can we avoid boxing/unboxing? - TValue current; - if (currentValue != null) - current = (TValue) Convert.ChangeType(currentValue, typeof(TValue)); - else - current = default; - - return Apply(current!); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/IDataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/IDataBindingModifier.cs deleted file mode 100644 index 6f920c0a1..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/IDataBindingModifier.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Modifies a data model value in a way defined by the modifier type - /// - public interface IDataBindingModifier : IStorageModel, IDisposable - { - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs deleted file mode 100644 index 233a5add3..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Artemis.Storage.Entities.Profile.DataBindings; - -namespace Artemis.Core -{ - /// - /// Represents a data binding mode that directly applies a data model value to a data binding - /// - public class DirectDataBinding : IDataBindingMode - { - private readonly List> _modifiers = new(); - private bool _disposed; - - internal DirectDataBinding(DataBinding dataBinding, DirectDataBindingEntity entity) - { - DataBinding = dataBinding; - Entity = entity; - Modifiers = new ReadOnlyCollection>(_modifiers); - Load(); - } - - /// - /// Gets the path of the source property - /// - public DataModelPath? SourcePath { get; private set; } - - /// - /// Gets a list of modifiers applied to this data binding - /// - public ReadOnlyCollection> Modifiers { get; } - - internal DirectDataBindingEntity Entity { get; } - - /// - public DataBinding DataBinding { get; } - - /// - public TProperty GetValue(TProperty baseValue) - { - if (_disposed) - throw new ObjectDisposedException("DirectDataBinding"); - - if (SourcePath == null || !SourcePath.IsValid || DataBinding.Converter == null) - return baseValue; - - object? dataBindingValue = SourcePath.GetValue(); - foreach (DataBindingModifier dataBindingModifier in Modifiers) - dataBindingValue = dataBindingModifier.Apply(dataBindingValue); - - return DataBinding.Converter.ConvertFromObject(dataBindingValue); - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _disposed = true; - - foreach (DataBindingModifier dataBindingModifier in Modifiers) - dataBindingModifier.Dispose(); - - SourcePath?.Dispose(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Storage - - /// - public void Load() - { - // Source - if (Entity.SourcePath != null) - SourcePath = new DataModelPath(null, Entity.SourcePath); - - // Modifiers - foreach (DataBindingModifierEntity dataBindingModifierEntity in Entity.Modifiers) - _modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity)); - - ApplyOrder(); - } - - /// - public void Save() - { - // Don't save an invalid state - if (SourcePath != null && !SourcePath.IsValid) - return; - - SourcePath?.Save(); - Entity.SourcePath = SourcePath?.Entity; - - // Modifiers - Entity.Modifiers.Clear(); - foreach (DataBindingModifier dataBindingModifier in Modifiers) - dataBindingModifier.Save(); - } - - #endregion - - #region Source - - /// - /// Returns the type of the source property of this data binding - /// - public Type? GetSourceType() - { - return SourcePath?.GetPropertyType(); - } - - /// - /// Updates the source of the data binding - /// - /// The path pointing to the source - public void UpdateSource(DataModelPath? path) - { - if (_disposed) - throw new ObjectDisposedException("DirectDataBinding"); - - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update source of data binding to an invalid path"); - - SourcePath?.Dispose(); - SourcePath = path != null ? new DataModelPath(path) : null; - } - - #endregion - - #region Modifiers - - /// - /// Adds a modifier to the direct data binding's collection - /// - /// The type of the parameter, can either be dynamic (based on a data model value) or static - public DataBindingModifier AddModifier(ProfileRightSideType type) - { - if (_disposed) - throw new ObjectDisposedException("DirectDataBinding"); - - DataBindingModifier modifier = new(this, type); - _modifiers.Add(modifier); - - ApplyOrder(); - OnModifiersUpdated(); - - return modifier; - } - - /// - /// Removes a modifier from the direct data binding's collection and disposes it - /// - public void RemoveModifier(DataBindingModifier modifier) - { - if (_disposed) - throw new ObjectDisposedException("DirectDataBinding"); - if (!_modifiers.Contains(modifier)) - return; - - _modifiers.Remove(modifier); - modifier.Dispose(); - - ApplyOrder(); - OnModifiersUpdated(); - } - - /// - /// Applies the current order of conditions to the collection - /// - public void ApplyOrder() - { - _modifiers.Sort((a, b) => a.Order.CompareTo(b.Order)); - for (int index = 0; index < _modifiers.Count; index++) - { - DataBindingModifier modifier = _modifiers[index]; - modifier.Order = index + 1; - } - } - - #endregion - - #region Events - - /// - /// Occurs when a modifier is added or removed - /// - public event EventHandler? ModifiersUpdated; - - /// - /// Invokes the event - /// - protected virtual void OnModifiersUpdated() - { - ModifiersUpdated?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs deleted file mode 100644 index e46a2aee9..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a data binding mode - /// - public interface IDataBindingMode : IStorageModel, IDisposable - { - /// - /// Gets the data binding this mode is applied to - /// - DataBinding DataBinding { get; } - - /// - /// Gets the current value of the data binding - /// - /// The base value of the property the data binding is applied to - /// - TProperty GetValue(TProperty baseValue); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index baa7eac6c..c6ee7ce90 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -72,9 +72,12 @@ namespace Artemis.Core SubscribeToDataModelStore(); } - internal DataModelPath(DataModel? target, DataModelPathEntity entity) + /// + /// Creates a new instance of the class based on a + /// + /// + public DataModelPath(DataModelPathEntity entity) { - Target = target; Path = entity.Path; Entity = entity; @@ -110,7 +113,10 @@ namespace Artemis.Core /// public IReadOnlyCollection Segments => _segments.ToList().AsReadOnly(); - internal DataModelPathEntity Entity { get; } + /// + /// Gets the entity used for persistent storage + /// + public DataModelPathEntity Entity { get; } internal Func? Accessor { get; private set; } @@ -173,6 +179,52 @@ namespace Artemis.Core return string.IsNullOrWhiteSpace(Path) ? "this" : Path; } + /// + /// Occurs whenever the path becomes invalid + /// + public event EventHandler? PathInvalidated; + + /// + /// Occurs whenever the path becomes valid + /// + public event EventHandler? PathValidated; + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _disposed = true; + + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + + Invalidate(); + } + } + + /// + /// Invokes the event + /// + protected virtual void OnPathValidated() + { + PathValidated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Invokes the event + /// + protected virtual void OnPathInvalidated() + { + PathInvalidated?.Invoke(this, EventArgs.Empty); + } + internal void Invalidate() { Target?.RemoveDataModelPath(this); @@ -262,27 +314,24 @@ namespace Artemis.Core DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; } - - #region IDisposable - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) + private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e) { - if (disposing) - { - _disposed = true; + if (e.Registration.DataModel.Module.Id != Entity.DataModelId) + return; - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + Invalidate(); + Target = e.Registration.DataModel; + Initialize(); + } - Invalidate(); - } + private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e) + { + if (e.Registration.DataModel.Module.Id != Entity.DataModelId) + return; + + Invalidate(); + Target = null; } /// @@ -292,8 +341,6 @@ namespace Artemis.Core GC.SuppressFinalize(this); } - #endregion - #region Storage /// @@ -314,68 +361,33 @@ namespace Artemis.Core Entity.Path = Path; Entity.DataModelId = DataModelId; + } - Entity.WrapperType = Target switch - { - ListPredicateWrapperDataModel _ => PathWrapperType.List, - EventPredicateWrapperDataModel _ => PathWrapperType.Event, - _ => PathWrapperType.None - }; + #region Equality members + + /// > + protected bool Equals(DataModelPath other) + { + return ReferenceEquals(Target, other.Target) && Path == other.Path; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DataModelPath) obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Target, Path); } #endregion - #region Event handlers - - private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e) - { - if (e.Registration.DataModel.Module.Id != Entity.DataModelId) - return; - - Invalidate(); - Target = e.Registration.DataModel; - Initialize(); - } - - private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e) - { - if (e.Registration.DataModel.Module.Id != Entity.DataModelId) - return; - - Invalidate(); - Target = null; - } - - #endregion - - #region Events - - /// - /// Occurs whenever the path becomes invalid - /// - public event EventHandler? PathInvalidated; - - /// - /// Occurs whenever the path becomes valid - /// - public event EventHandler? PathValidated; - - /// - /// Invokes the event - /// - protected virtual void OnPathValidated() - { - PathValidated?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invokes the event - /// - protected virtual void OnPathInvalidated() - { - PathInvalidated?.Invoke(this, EventArgs.Empty); - } - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index 5c70eae3f..19c1d87ab 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -295,7 +295,10 @@ namespace Artemis.Core private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e) { if (e.Key == Identifier) + { + DataModelPath.Invalidate(); DataModelPath.Initialize(); + } } private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) diff --git a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs index 4357dc3c6..4ab9d271c 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Artemis.Core { /// - /// Represents a data model event that can trigger s. + /// Represents an event that is part of a data model /// public interface IDataModelEvent { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index dcfcdeeda..52ae56bf1 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -23,6 +22,26 @@ namespace Artemis.Core /// LayerPropertyGroup LayerPropertyGroup { get; } + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden { get; set; } + + /// + /// Gets the data binding of this property + /// + IDataBinding BaseDataBinding { get; } + + /// + /// Gets a boolean indicating whether the layer has any data binding properties + /// + public bool HasDataBinding { get; } + + /// + /// Gets a boolean indicating whether data bindings are supported on this type of property + /// + public bool DataBindingsSupported { get; } + /// /// Gets the unique path of the property on the layer /// @@ -47,11 +66,6 @@ namespace Artemis.Core /// void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path); - /// - /// Returns a list off all data binding registrations - /// - List GetAllDataBindingRegistrations(); - /// /// Attempts to load and add the provided keyframe entity to the layer property /// @@ -70,6 +84,12 @@ namespace Artemis.Core /// The timeline to apply to the property void Update(Timeline timeline); + + /// + /// Updates just the data binding instead of the entire layer + /// + void UpdateDataBinding(); + /// /// Occurs when the layer property is disposed /// @@ -104,25 +124,5 @@ namespace Artemis.Core /// Occurs when a keyframe was removed from the layer property /// public event EventHandler? KeyframeRemoved; - - /// - /// Occurs when a data binding property has been added - /// - public event EventHandler? DataBindingPropertyRegistered; - - /// - /// Occurs when all data binding properties have been removed - /// - public event EventHandler? DataBindingPropertiesCleared; - - /// - /// Occurs when a data binding has been enabled - /// - public event EventHandler? DataBindingEnabled; - - /// - /// Occurs when a data binding has been disabled - /// - public event EventHandler? DataBindingDisabled; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 8a4cc1d7c..c89abca04 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -30,6 +30,7 @@ namespace Artemis.Core Path = null!; Entity = null!; PropertyDescription = null!; + DataBinding = null!; CurrentValue = default!; DefaultValue = default!; @@ -58,93 +59,10 @@ namespace Artemis.Core { _disposed = true; - foreach (IDataBinding dataBinding in _dataBindings) - dataBinding.Dispose(); - + DataBinding.Dispose(); Disposed?.Invoke(this, EventArgs.Empty); } - /// - /// Invokes the event - /// - protected virtual void OnUpdated() - { - Updated?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnCurrentValueSet() - { - CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); - LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnVisibilityChanged() - { - VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframesToggled() - { - KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframeAdded() - { - KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframeRemoved() - { - KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingPropertyRegistered() - { - DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingPropertiesCleared() - { - DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e) - { - DataBindingEnabled?.Invoke(this, e); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e) - { - DataBindingDisabled?.Invoke(this, e); - } - /// public PropertyDescriptionAttribute PropertyDescription { get; internal set; } @@ -163,7 +81,16 @@ namespace Artemis.Core CurrentValue = BaseValue; UpdateKeyframes(timeline); - UpdateDataBindings(timeline); + UpdateDataBinding(); + + // UpdateDataBinding called OnUpdated() + } + + /// + public void UpdateDataBinding() + { + DataBinding.Update(); + DataBinding.Apply(); OnUpdated(); } @@ -175,46 +102,11 @@ namespace Artemis.Core GC.SuppressFinalize(this); } - /// - public event EventHandler? Disposed; - - /// - public event EventHandler? Updated; - - /// - public event EventHandler? CurrentValueSet; - - /// - public event EventHandler? VisibilityChanged; - - /// - public event EventHandler? KeyframesToggled; - - /// - public event EventHandler? KeyframeAdded; - - /// - public event EventHandler? KeyframeRemoved; - - /// - public event EventHandler? DataBindingPropertyRegistered; - - /// - public event EventHandler? DataBindingPropertiesCleared; - - /// - public event EventHandler? DataBindingEnabled; - - /// - public event EventHandler? DataBindingDisabled; - #region Hierarchy private bool _isHidden; - /// - /// Gets or sets whether the property is hidden in the UI - /// + /// public bool IsHidden { get => _isHidden; @@ -481,137 +373,19 @@ namespace Artemis.Core #region Data bindings - // ReSharper disable InconsistentNaming - internal readonly List _dataBindingRegistrations = new(); - - internal readonly List _dataBindings = new(); - // ReSharper restore InconsistentNaming - /// - /// Gets whether data bindings are supported on this type of property + /// Gets the data binding of this property /// - public bool DataBindingsSupported { get; protected internal set; } = true; + public DataBinding DataBinding { get; private set; } - /// - /// Gets whether the layer has any active data bindings - /// - public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); + /// + public bool DataBindingsSupported => DataBinding.Properties.Any(); - /// - /// Gets a data binding registration by the display name used to register it - /// Note: The expression must exactly match the one used to register the data binding - /// - public DataBindingRegistration? GetDataBindingRegistration(string identifier) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); + /// + public IDataBinding BaseDataBinding => DataBinding; - IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.DisplayName == identifier); - return (DataBindingRegistration?) match; - } - - /// - /// Gets a list containing all data binding registrations of this layer property - /// - /// A list containing all data binding registrations of this layer property - public List GetAllDataBindingRegistrations() - { - return _dataBindingRegistrations; - } - - /// - /// Registers a data binding property so that is available to the data binding system - /// - /// The type of the layer property - /// The function to call to get the value of the property - /// The action to call to set the value of the property - /// The converter to use while applying the data binding - /// The display name of the data binding property - public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, - string displayName) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName)) - throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); - - DataBindingRegistration registration = new(this, converter, getter, setter, displayName); - _dataBindingRegistrations.Add(registration); - - // If not yet initialized, load the data binding related to the registration if available - if (_isInitialized) - { - IDataBinding? dataBinding = registration.CreateDataBinding(); - if (dataBinding != null) - _dataBindings.Add(dataBinding); - } - - OnDataBindingPropertyRegistered(); - return registration; - } - - /// - /// Removes all data binding properties so they are no longer available to the data binding system - /// - public void ClearDataBindingProperties() - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations) - dataBindingRegistration.ClearDataBinding(); - _dataBindingRegistrations.Clear(); - - OnDataBindingPropertiesCleared(); - } - - /// - /// Enables a data binding for the provided - /// - /// The newly created data binding - public DataBinding EnableDataBinding(DataBindingRegistration dataBindingRegistration) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - if (dataBindingRegistration.LayerProperty != this) - throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property"); - if (dataBindingRegistration.DataBinding != null) - throw new ArtemisCoreException("Provided data binding registration already has an enabled data binding"); - - DataBinding dataBinding = new(dataBindingRegistration); - _dataBindings.Add(dataBinding); - - OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); - return dataBinding; - } - - /// - /// Disables the provided data binding - /// - /// The data binding to remove - public void DisableDataBinding(DataBinding dataBinding) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - _dataBindings.Remove(dataBinding); - - if (dataBinding.Registration != null) - dataBinding.Registration.DataBinding = null; - dataBinding.Dispose(); - OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); - } - - private void UpdateDataBindings(Timeline timeline) - { - foreach (IDataBinding dataBinding in _dataBindings) - { - dataBinding.Update(timeline); - dataBinding.Apply(); - } - } + /// + public bool HasDataBinding => DataBinding.IsEnabled; #endregion @@ -695,6 +469,7 @@ namespace Artemis.Core Entity = entity ?? throw new ArgumentNullException(nameof(entity)); PropertyDescription = description ?? throw new ArgumentNullException(nameof(description)); IsLoadedFromStorage = fromStorage; + DataBinding = Entity.DataBinding != null ? new DataBinding(this, Entity.DataBinding) : new DataBinding(this); if (PropertyDescription.DisableKeyframes) KeyframesSupported = false; @@ -738,13 +513,7 @@ namespace Artemis.Core // ignored for now } - _dataBindings.Clear(); - foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations) - { - IDataBinding? dataBinding = dataBindingRegistration.CreateDataBinding(); - if (dataBinding != null) - _dataBindings.Add(dataBinding); - } + DataBinding.Load(); } /// @@ -763,9 +532,8 @@ namespace Artemis.Core Entity.KeyframeEntities.Clear(); Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity())); - Entity.DataBindingEntities.Clear(); - foreach (IDataBinding dataBinding in _dataBindings) - dataBinding.Save(); + DataBinding.Save(); + Entity.DataBinding = DataBinding.Entity; } /// @@ -776,5 +544,79 @@ namespace Artemis.Core } #endregion + + #region Events + + /// + public event EventHandler? Disposed; + + /// + public event EventHandler? Updated; + + /// + public event EventHandler? CurrentValueSet; + + /// + public event EventHandler? VisibilityChanged; + + /// + public event EventHandler? KeyframesToggled; + + /// + public event EventHandler? KeyframeAdded; + + /// + public event EventHandler? KeyframeRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnUpdated() + { + Updated?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnCurrentValueSet() + { + CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); + LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnVisibilityChanged() + { + VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframesToggled() + { + KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframeAdded() + { + KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframeRemoved() + { + KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index c0c3a2528..a9090e12d 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -206,12 +206,10 @@ namespace Artemis.Core } } + List renderElements = GetAllRenderElements(); + if (ProfileEntity.LastSelectedProfileElement != Guid.Empty) - { - LastSelectedProfileElement = GetAllFolders().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - if (LastSelectedProfileElement == null) - LastSelectedProfileElement = GetAllLayers().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - } + LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); else LastSelectedProfileElement = null; @@ -219,6 +217,10 @@ namespace Artemis.Core scriptConfiguration.Script?.Dispose(); ScriptConfigurations.Clear(); ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); + + // Load node scripts last since they may rely on the profile structure being in place + foreach (RenderProfileElement renderProfileElement in renderElements) + renderProfileElement.LoadNodeScript(); } internal override void Save() @@ -253,10 +255,7 @@ namespace Artemis.Core /// public override IEnumerable GetBrokenHierarchy() { - foreach (IBreakableModel breakableModel in GetAllFolders().SelectMany(folders => folders.GetBrokenHierarchy())) - yield return breakableModel; - foreach (IBreakableModel breakableModel in GetAllLayers().SelectMany(layer => layer.GetBrokenHierarchy())) - yield return breakableModel; + return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy()); } #endregion diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 61b8f4f03..166c3b4b7 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -91,6 +91,13 @@ namespace Artemis.Core /// public bool Disposed { get; protected set; } + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => Name ?? GetType().Name; + + #endregion + /// /// Updates the element /// @@ -113,13 +120,6 @@ namespace Artemis.Core return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; } - #region Overrides of BreakableModel - - /// - public override string BrokenDisplayName => Name ?? GetType().Name; - - #endregion - #region Hierarchy /// @@ -139,7 +139,9 @@ namespace Artemis.Core // Add to the end of the list if (order == null) + { ChildrenList.Add(child); + } // Insert at the given index else { @@ -183,6 +185,27 @@ namespace Artemis.Core ChildrenList[index].Order = index; } + /// + /// Returns a flattened list of all child render elements + /// + /// + public List GetAllRenderElements() + { + if (Disposed) + throw new ObjectDisposedException(GetType().Name); + + List elements = new(); + foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast()) + { + // Add all folders in this element + elements.Add(childElement); + // Add all folders in folders inside this element + elements.AddRange(childElement.GetAllRenderElements()); + } + + return elements; + } + /// /// Returns a flattened list of all child folders /// @@ -232,27 +255,6 @@ namespace Artemis.Core internal abstract void Load(); internal abstract void Save(); - #endregion - - #region IDisposable - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the profile element - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - #endregion #region Events @@ -284,5 +286,26 @@ namespace Artemis.Core } #endregion + + #region IDisposable + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the profile element + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index ff317d582..6428c82e4 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -7,6 +7,7 @@ using Artemis.Core.LayerEffects.Placeholder; using Artemis.Core.Properties; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Conditions; using SkiaSharp; namespace Artemis.Core @@ -60,21 +61,25 @@ namespace Artemis.Core foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); - DisplayCondition?.Dispose(); + if (DisplayCondition is IDisposable disposable) + disposable.Dispose(); base.Dispose(disposing); } internal void LoadRenderElement() { - DisplayCondition = RenderElementEntity.DisplayCondition != null - ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition) - : new DataModelConditionGroup(null); - Timeline = RenderElementEntity.Timeline != null ? new Timeline(RenderElementEntity.Timeline) : new Timeline(); + DisplayCondition = RenderElementEntity.DisplayCondition switch + { + StaticConditionEntity staticConditionEntity => new StaticCondition(staticConditionEntity, this), + EventConditionEntity eventConditionEntity => new EventCondition(eventConditionEntity, this), + _ => DisplayCondition + }; + ActivateEffects(); } @@ -97,15 +102,24 @@ namespace Artemis.Core layerEffect.BaseProperties?.ApplyToEntity(); } - // Conditions - RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; + // Condition DisplayCondition?.Save(); + RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; // Timeline RenderElementEntity.Timeline = Timeline?.Entity; Timeline?.Save(); } + internal void LoadNodeScript() + { + if (DisplayCondition is INodeScriptCondition scriptCondition) + scriptCondition.LoadNodeScript(); + + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + layerProperty.BaseDataBinding.LoadNodeScript(); + } + internal void OnLayerEffectsUpdated() { LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); @@ -124,10 +138,10 @@ namespace Artemis.Core /// public void UpdateTimeline(double deltaTime) { + // TODO: Move to conditions + // The play mode dictates whether to stick to the main segment unless the display conditions contains events - bool stickToMainSegment = (Timeline.PlayMode == TimelinePlayMode.Repeat || Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) && DisplayConditionMet; - if (DisplayCondition != null && DisplayCondition.ContainsEvents && Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle) - stickToMainSegment = false; + bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet; Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment); } @@ -355,19 +369,19 @@ namespace Artemis.Core protected set => SetAndNotify(ref _displayConditionMet, value); } - private DataModelConditionGroup? _displayCondition; private bool _displayConditionMet; - private bool _toggledOnByEvent = false; /// - /// Gets or sets the root display condition group + /// Gets the display condition used to determine whether this element is active or not /// - public DataModelConditionGroup? DisplayCondition + public ICondition? DisplayCondition { get => _displayCondition; set => SetAndNotify(ref _displayCondition, value); } + private ICondition? _displayCondition; + /// /// Evaluates the display conditions on this element and applies any required changes to the /// @@ -385,54 +399,9 @@ namespace Artemis.Core return; } - if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle) - _toggledOnByEvent = false; - - bool conditionMet = DisplayCondition.Evaluate(); - if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet) - conditionMet = false; - - if (!DisplayCondition.ContainsEvents) - { - // Regular conditions reset the timeline whenever their condition is met and was not met before that - if (conditionMet && !DisplayConditionMet && Timeline.IsFinished) - Timeline.JumpToStart(); - // If regular conditions are no longer met, jump to the end segment if stop mode requires it - if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd) - Timeline.JumpToEndSegment(); - } - else if (conditionMet) - { - if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) - { - _toggledOnByEvent = !_toggledOnByEvent; - if (_toggledOnByEvent) - Timeline.JumpToStart(); - } - else - { - // Event conditions reset if the timeline finished - if (Timeline.IsFinished) - { - Timeline.JumpToStart(); - } - // and otherwise apply their overlap mode - else - { - if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart) - Timeline.JumpToStart(); - else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy) - Timeline.AddExtraTimeline(); - // The third option is ignore which is handled below: - - // done - } - } - } - - DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle - ? _toggledOnByEvent - : conditionMet; + DisplayCondition.Update(); + DisplayCondition.ApplyToTimeline(DisplayCondition.IsMet, DisplayConditionMet, Timeline); + DisplayConditionMet = DisplayCondition.IsMet; } #endregion diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs index f090d53a9..86c9023d7 100644 --- a/src/Artemis.Core/Models/Profile/Timeline.cs +++ b/src/Artemis.Core/Models/Profile/Timeline.cs @@ -13,7 +13,7 @@ namespace Artemis.Core public class Timeline : CorePropertyChanged, IStorageModel { private const int MaxExtraTimelines = 15; - private readonly object _lock = new (); + private readonly object _lock = new(); /// /// Creates a new instance of the class @@ -82,7 +82,6 @@ namespace Artemis.Core private TimeSpan _position; private TimeSpan _lastDelta; - private TimeLineEventOverlapMode _eventOverlapMode; private TimelinePlayMode _playMode; private TimelineStopMode _stopMode; private readonly List _extraTimelines; @@ -139,17 +138,7 @@ namespace Artemis.Core get => _stopMode; set => SetAndNotify(ref _stopMode, value); } - - /// - /// Gets or sets the mode in which the render element responds to display condition events being fired before the - /// timeline finished - /// - public TimeLineEventOverlapMode EventOverlapMode - { - get => _eventOverlapMode; - set => SetAndNotify(ref _eventOverlapMode, value); - } - + /// /// Gets a list of extra copies of the timeline applied to this timeline /// @@ -326,7 +315,7 @@ namespace Artemis.Core IsOverridden = false; _lastOverridePosition = Position; - + if (stickToMainSegment && Position > MainSegmentEndPosition) { // If the main segment has no length, simply stick to the start of the segment @@ -445,7 +434,8 @@ namespace Artemis.Core EndSegmentLength = Entity.EndSegmentLength; PlayMode = (TimelinePlayMode) Entity.PlayMode; StopMode = (TimelineStopMode) Entity.StopMode; - EventOverlapMode = (TimeLineEventOverlapMode) Entity.EventOverlapMode; + + JumpToEnd(); } /// @@ -456,7 +446,6 @@ namespace Artemis.Core Entity.EndSegmentLength = EndSegmentLength; Entity.PlayMode = (int) PlayMode; Entity.StopMode = (int) StopMode; - Entity.EventOverlapMode = (int) EventOverlapMode; } #endregion diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index 21e1a3157..7ddc0f5f5 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -9,7 +9,7 @@ namespace Artemis.Core /// /// Represents the configuration of a profile, contained in a /// - public class ProfileConfiguration : CorePropertyChanged, IStorageModel, IDisposable + public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable { private ProfileCategory _category; private bool _disposed; @@ -28,6 +28,7 @@ namespace Artemis.Core Entity = new ProfileConfigurationEntity(); Icon = new ProfileConfigurationIcon(Entity) {MaterialIcon = icon}; + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active", this); } internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity) @@ -38,6 +39,8 @@ namespace Artemis.Core Entity = entity; Icon = new ProfileConfigurationIcon(Entity); + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active", this); + Load(); } @@ -130,7 +133,7 @@ namespace Artemis.Core /// Gets the data model condition that must evaluate to for this profile to be activated /// alongside any activation requirements of the , if set /// - public DataModelConditionGroup? ActivationCondition { get; set; } + public NodeScript ActivationCondition { get; set; } /// /// Gets or sets the module this profile uses @@ -168,7 +171,13 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate(); + if (!ActivationCondition.ExitNodeConnected) + ActivationConditionMet = true; + else + { + ActivationCondition.Run(); + ActivationConditionMet = ActivationCondition.Result; + } } /// @@ -210,7 +219,7 @@ namespace Artemis.Core public void Dispose() { _disposed = true; - ActivationCondition?.Dispose(); + ActivationCondition.Dispose(); } #endregion @@ -231,7 +240,10 @@ namespace Artemis.Core Icon.Load(); - ActivationCondition = Entity.ActivationCondition != null ? new DataModelConditionGroup(null, Entity.ActivationCondition) : null; + ActivationCondition.Dispose(); + ActivationCondition = Entity.ActivationCondition != null + ? new NodeScript("Activate profile", "Whether or not the profile should be active", Entity.ActivationCondition, this) + : new NodeScript("Activate profile", "Whether or not the profile should be active", this); EnableHotkey = Entity.EnableHotkey != null ? new ProfileConfigurationHotkey(Entity.EnableHotkey) : null; DisableHotkey = Entity.DisableHotkey != null ? new ProfileConfigurationHotkey(Entity.DisableHotkey) : null; @@ -265,6 +277,13 @@ namespace Artemis.Core } #endregion + + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => "Profile Configuration"; + + #endregion } /// diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index ffbb45149..d6dbfff46 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -114,17 +114,20 @@ namespace Artemis.Core.Modules private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new(); private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new(); + /// + /// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y) + /// + protected internal readonly List HiddenPropertiesList = new(); + + /// + /// The base constructor of the class. + /// protected Module() { DefaultProfilePaths = new ReadOnlyCollection<(DefaultCategoryName, string)>(_defaultProfilePaths); HiddenProperties = new ReadOnlyCollection(HiddenPropertiesList); } - /// - /// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y) - /// - protected internal readonly List HiddenPropertiesList = new(); - /// /// Gets a read only collection of default profile paths /// diff --git a/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs index 0d51253e4..1f8d4b1c4 100644 --- a/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs +++ b/src/Artemis.Core/Plugins/ScriptingProviders/ScriptingProvider.cs @@ -44,6 +44,9 @@ namespace Artemis.Core.ScriptingProviders /// public abstract class ScriptingProvider : PluginFeature { + /// + /// The base constructor of the class + /// protected ScriptingProvider() { Scripts = new ReadOnlyCollection