mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Conditions - Removed old systems
Data bindings - Removed old systems Data bindings - Implemented node system
This commit is contained in:
parent
32d4ec2812
commit
5675d1895b
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EnumContainsConditionOperator : ConditionOperator<Enum, Enum>
|
||||
{
|
||||
public override string Description => "Contains";
|
||||
public override string Icon => "Contain";
|
||||
|
||||
public override bool Evaluate(Enum a, Enum b)
|
||||
{
|
||||
return a != null && b != null && a.HasFlag(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EnumNotContainsConditionOperator : ConditionOperator<Enum, Enum>
|
||||
{
|
||||
public override string Description => "Does not contain";
|
||||
public override string Icon => "FormatStrikethrough";
|
||||
|
||||
public override bool Evaluate(Enum a, Enum b)
|
||||
{
|
||||
return a != null && (b == null || !a.HasFlag(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EqualsConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class GreaterThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is greater than";
|
||||
public override string Icon => "GreaterThan";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class GreaterThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is greater than or equal to";
|
||||
public override string Icon => "GreaterThanOrEqual";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a >= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LessThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is less than";
|
||||
public override string Icon => "LessThan";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LessThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is less than or equal to";
|
||||
public override string Icon => "LessThanOrEqual";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NotEqualConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NotNullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
public override string Description => "Is not null";
|
||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
||||
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
public override string Description => "Is null";
|
||||
public override string Icon => "Null";
|
||||
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NumberEqualsConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) < 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NumberNotEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) > 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Contains";
|
||||
public override string Icon => "Contain";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringEndsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Ends with";
|
||||
public override string Icon => "ContainEnd";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.EndsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringEqualsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Artemis.Core {
|
||||
internal class StringMatchesRegexConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Matches Regex";
|
||||
public override string Icon => "Regex";
|
||||
|
||||
public override bool Evaluate(string text, string regex)
|
||||
{
|
||||
// Ensures full match
|
||||
if (!regex.StartsWith("^"))
|
||||
regex = "^" + regex;
|
||||
if (!regex.EndsWith("$"))
|
||||
regex += "$";
|
||||
|
||||
return Regex.IsMatch(text, regex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Does not contain";
|
||||
public override string Icon => "FormatStrikethrough";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotEqualConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return !string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotNullConditionOperator : ConditionOperator<string>
|
||||
{
|
||||
public override string Description => "Is not null";
|
||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
||||
|
||||
public override bool Evaluate(string a)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNullConditionOperator : ConditionOperator<string>
|
||||
{
|
||||
public override string Description => "Is null";
|
||||
public override string Icon => "Null";
|
||||
|
||||
public override bool Evaluate(string a)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringStartsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Starts with";
|
||||
public override string Icon => "ContainStart";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.StartsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorBrightenModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Brighten by";
|
||||
public override string Icon => "CarLightHigh";
|
||||
public override string Description => "Brightens the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
l *= (parameterValue + 100f) / 100f;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorDarkenModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Darken by";
|
||||
public override string Icon => "CarLightDimmed";
|
||||
public override string Description => "Darkens the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
l *= (parameterValue * -1 + 100f) / 100f;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorDesaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Desaturate";
|
||||
public override string Icon => "ImageMinus";
|
||||
public override string Description => "Desaturates the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
s -= parameterValue;
|
||||
s = Math.Clamp(s, 0, 100);
|
||||
|
||||
return SKColor.FromHsl(h, s, l, currentValue.Alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorInvertModifierType : DataBindingModifierType<SKColor>
|
||||
{
|
||||
public override string Name => "Invert color";
|
||||
public override string Icon => "InvertColors";
|
||||
public override string Description => "Inverts the color by rotating its hue by a 180°";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
h += 180;
|
||||
return SKColor.FromHsl(h % 360, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorRotateHueModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Rotate Hue by";
|
||||
public override string Icon => "RotateRight";
|
||||
public override string Description => "Rotates the hue of the color by the amount in degrees";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
h += parameterValue;
|
||||
return SKColor.FromHsl(h % 360, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorSaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Saturate";
|
||||
public override string Icon => "ImagePlus";
|
||||
public override string Description => "Saturates the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsv(out float h, out float s, out float v);
|
||||
s += parameterValue;
|
||||
s = Math.Clamp(s, 0, 100);
|
||||
|
||||
return SKColor.FromHsv(h, s, v, currentValue.Alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorSumModifierType : DataBindingModifierType<SKColor, SKColor>
|
||||
{
|
||||
public override string Name => "Combine with";
|
||||
public override string Icon => "FormatColorFill";
|
||||
public override string Description => "Adds the two colors together";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, SKColor parameterValue)
|
||||
{
|
||||
return currentValue.Sum(parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class AbsoluteModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Absolute";
|
||||
public override string Icon => "NumericPositive1";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Converts the input value to an absolute value";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Abs(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DivideModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Divide by";
|
||||
public override string Icon => "Divide";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
// Ye ye none of that
|
||||
if (parameterValue == 0)
|
||||
return 0;
|
||||
|
||||
return currentValue / parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MaxModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Max";
|
||||
public override string Icon => "ChevronUpBoxOutline";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Keeps only the largest of input value and parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Max(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MinModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Min";
|
||||
public override string Icon => "ChevronDownBoxOutline";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Keeps only the smallest of input value and parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Min(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ModuloModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Modulo";
|
||||
public override string Icon => "Stairs";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Calculates the remained of the division between the input value and the parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue % parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MultiplicationModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Multiply by";
|
||||
public override string Icon => "Close";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue * parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class PercentageOfModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Percentage of";
|
||||
public override string Icon => "Percent";
|
||||
public override string Description => "Calculates how much percent the parameter value is of the current value";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
// Ye ye none of that
|
||||
if (parameterValue == 0d)
|
||||
return 100d;
|
||||
|
||||
return 100d / parameterValue * currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class PowerModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Power";
|
||||
public override string Icon => "Exponent";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Raises the input value to the power of the parameter value";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Pow(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CeilingModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round up";
|
||||
public override string Icon => "ArrowUp";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Ceils the input, rounding it up to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Ceiling(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class FloorModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round down";
|
||||
public override string Icon => "ArrowDown";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Floors the input, rounding it down to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Floor(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class RoundModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round";
|
||||
public override string Icon => "ArrowCollapse";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Rounds the input to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Round(currentValue, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SquareRootModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Square root";
|
||||
public override string Icon => "SquareRoot";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Calculates square root of the input value";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Sqrt(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SubtractModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Subtract";
|
||||
public override string Icon => "Minus";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue - parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SumModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Sum";
|
||||
public override string Icon => "Plus";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue + parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CosecantModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cosecant";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cosecant";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Sin(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CosineModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cosine";
|
||||
public override string Icon => "MathCos";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cosine";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Cos(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CotangentModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cotangent";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cotangent";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Tan(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SecantModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Secant";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the secant";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Cos(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SineModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Sine";
|
||||
public override string Icon => "MathSin";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the sine";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Sin(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class TangentModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Tangent";
|
||||
public override string Icon => "MathTan";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the tangent";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Tan(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ConditionOperatorStoreEvent
|
||||
{
|
||||
public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
|
||||
public ConditionOperatorRegistration Registration { get; }
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DataBindingModifierTypeStoreEvent
|
||||
{
|
||||
public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration)
|
||||
{
|
||||
TypeRegistration = typeRegistration;
|
||||
}
|
||||
|
||||
public DataBindingModifierTypeRegistration TypeRegistration { get; }
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation
|
||||
/// <para>
|
||||
/// To implement your own condition operator, inherit <see cref="ConditionOperator{TLeftSide, TRightSide}" /> or
|
||||
/// <see cref="ConditionOperator{TLeftSide}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class BaseConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this condition operator belongs to
|
||||
/// <para>Note: Not set until after registering</para>
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left side type of this condition operator
|
||||
/// </summary>
|
||||
public abstract Type LeftSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right side type of this condition operator. May be null if the operator does not support a right side
|
||||
/// </summary>
|
||||
public abstract Type? RightSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type is supported by the operator
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
|
||||
/// <param name="side">Which side of the operator to check, left or right</param>
|
||||
public bool SupportsType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
if (type == null)
|
||||
return true;
|
||||
if (side == ConditionParameterSide.Left)
|
||||
return LeftSideType.IsCastableFrom(type);
|
||||
return RightSideType != null && RightSideType.IsCastableFrom(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition with the input types being provided as objects
|
||||
/// <para>
|
||||
/// This leaves the caller responsible for the types matching <see cref="LeftSideType" /> and
|
||||
/// <see cref="RightSideType" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="leftSideValue">The left side value, type should match <see cref="LeftSideType" /></param>
|
||||
/// <param name="rightSideValue">The right side value, type should match <see cref="RightSideType" /></param>
|
||||
/// <returns>The result of the boolean condition's evaluation</returns>
|
||||
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a side of a condition parameter
|
||||
/// </summary>
|
||||
public enum ConditionParameterSide
|
||||
{
|
||||
/// <summary>
|
||||
/// The left side of a condition parameter
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// The right side of a condition parameter
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using a left- and right-side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type RightSideType => typeof(TRightSide);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
/// <param name="b">The parameter on the right side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
{
|
||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = (TLeftSide) leftSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftSide = default;
|
||||
}
|
||||
|
||||
TRightSide rightSide;
|
||||
if (rightSideValue != null)
|
||||
{
|
||||
if (rightSideValue.GetType() != typeof(TRightSide) && leftSideValue is IConvertible)
|
||||
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
|
||||
else
|
||||
rightSide = (TRightSide) rightSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightSide = default;
|
||||
}
|
||||
|
||||
return Evaluate(leftSide!, rightSide!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using only a left side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <summary>
|
||||
/// Always <c>null</c>, not applicable to this type of condition operator
|
||||
/// </summary>
|
||||
public override Type? RightSideType => null;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
{
|
||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = (TLeftSide) leftSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftSide = default;
|
||||
}
|
||||
|
||||
return Evaluate(leftSide!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract class for display condition parts
|
||||
/// </summary>
|
||||
public abstract class DataModelConditionPart : IDisposable
|
||||
{
|
||||
private readonly List<DataModelConditionPart> _children = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this part
|
||||
/// </summary>
|
||||
public DataModelConditionPart? Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of this part
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<DataModelConditionPart> Children => _children.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a child to the display condition part's <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="dataModelConditionPart"></param>
|
||||
/// <param name="index">An optional index at which to insert the condition</param>
|
||||
public void AddChild(DataModelConditionPart dataModelConditionPart, int? index = null)
|
||||
{
|
||||
if (!_children.Contains(dataModelConditionPart))
|
||||
{
|
||||
dataModelConditionPart.Parent = this;
|
||||
if (index != null)
|
||||
_children.Insert(index.Value, dataModelConditionPart);
|
||||
else
|
||||
_children.Add(dataModelConditionPart);
|
||||
|
||||
OnChildAdded();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a child from the display condition part's <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="dataModelConditionPart">The child to remove</param>
|
||||
public void RemoveChild(DataModelConditionPart dataModelConditionPart)
|
||||
{
|
||||
if (_children.Contains(dataModelConditionPart))
|
||||
{
|
||||
dataModelConditionPart.Parent = null;
|
||||
_children.Remove(dataModelConditionPart);
|
||||
OnChildRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all children. You monster.
|
||||
/// </summary>
|
||||
public void ClearChildren()
|
||||
{
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the data model
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract bool Evaluate();
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the given target (currently only for lists)
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
internal abstract bool EvaluateObject(object? target);
|
||||
|
||||
internal abstract void Save();
|
||||
internal abstract DataModelConditionPartEntity GetEntity();
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Disposed the condition part
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child-condition was added
|
||||
/// </summary>
|
||||
public event EventHandler? ChildAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child-condition was removed
|
||||
/// </summary>
|
||||
public event EventHandler? ChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokers the <see cref="ChildAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildAdded()
|
||||
{
|
||||
ChildAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokers the <see cref="ChildRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildRemoved()
|
||||
{
|
||||
ChildRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,411 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||
/// static value
|
||||
/// </summary>
|
||||
public abstract class DataModelConditionPredicate : DataModelConditionPart
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
/// <param name="entity">A new empty entity</param>
|
||||
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
PredicateType = predicateType;
|
||||
}
|
||||
|
||||
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the predicate type
|
||||
/// </summary>
|
||||
public ProfileRightSideType PredicateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operator
|
||||
/// </summary>
|
||||
public BaseConditionOperator? Operator { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the left property
|
||||
/// </summary>
|
||||
public DataModelPath? LeftPath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the right property
|
||||
/// </summary>
|
||||
public DataModelPath? RightPath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
||||
/// <see cref="ProfileRightSideType.Static" />
|
||||
/// </summary>
|
||||
public object? RightStaticValue { get; protected set; }
|
||||
|
||||
internal DataModelConditionPredicateEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}";
|
||||
return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}";
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
||||
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||
|
||||
LeftPath?.Dispose();
|
||||
RightPath?.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
|
||||
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||
|
||||
InitializeLeftPath();
|
||||
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
InitializeRightPath();
|
||||
// Right side static
|
||||
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the left path is not valid we cannot reliably set up the right side because the type is unknown
|
||||
// Because of that wait for it to validate first
|
||||
if (LeftPath != null && !LeftPath.IsValid)
|
||||
{
|
||||
LeftPath.PathValidated += InitializeRightSideStatic;
|
||||
return;
|
||||
}
|
||||
if (LeftPath == null)
|
||||
return;
|
||||
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
object? rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
catch (JsonReaderException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRightSideStatic(object? sender, EventArgs args)
|
||||
{
|
||||
if (LeftPath == null)
|
||||
return;
|
||||
|
||||
LeftPath.PathValidated -= InitializeRightSideStatic;
|
||||
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
object? rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the left path of this condition predicate
|
||||
/// </summary>
|
||||
protected abstract void InitializeLeftPath();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the right path of this condition predicate
|
||||
/// </summary>
|
||||
protected abstract void InitializeRightPath();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <summary>
|
||||
/// Updates the left side of the predicate
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the left side value inside the data model</param>
|
||||
public virtual void UpdateLeftSide(DataModelPath? path)
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
|
||||
|
||||
LeftPath?.Dispose();
|
||||
LeftPath = path != null ? new DataModelPath(path) : null;
|
||||
|
||||
ValidateOperator();
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the right side value inside the data model</param>
|
||||
public void UpdateRightSideDynamic(DataModelPath? path)
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
|
||||
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
|
||||
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
|
||||
|
||||
RightPath?.Dispose();
|
||||
RightPath = path != null ? new DataModelPath(path) : null;
|
||||
|
||||
PredicateType = ProfileRightSideType.Dynamic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="staticValue">The right side value to use</param>
|
||||
public void UpdateRightSideStatic(object? staticValue)
|
||||
{
|
||||
PredicateType = ProfileRightSideType.Static;
|
||||
RightPath?.Dispose();
|
||||
RightPath = null;
|
||||
|
||||
// If the operator is null simply apply the value, any validation will wait
|
||||
if (Operator == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the operator does not support a right side, always set it to null
|
||||
if (Operator.RightSideType == null)
|
||||
{
|
||||
RightStaticValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
Type? preferredType = GetPreferredRightSideType();
|
||||
if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, preferredType);
|
||||
// If null create a default instance for value types or simply make it null for reference types
|
||||
else if (preferredType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(preferredType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the operator of the predicate and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="conditionOperator"></param>
|
||||
public void UpdateOperator(BaseConditionOperator? conditionOperator)
|
||||
{
|
||||
if (conditionOperator == null)
|
||||
{
|
||||
Operator = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to check for compatibility without a left side, when left site does get set it will be checked again
|
||||
if (LeftPath == null || !LeftPath.IsValid)
|
||||
{
|
||||
Operator = conditionOperator;
|
||||
return;
|
||||
}
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
// Left side can't go empty so enforce a match
|
||||
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
|
||||
$"it does not support left side type {leftType.Name}");
|
||||
|
||||
Operator = conditionOperator;
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the best type to use for the right side op this predicate
|
||||
/// </summary>
|
||||
public abstract Type? GetPreferredRightSideType();
|
||||
|
||||
private void ValidateOperator()
|
||||
{
|
||||
if (LeftPath == null || !LeftPath.IsValid || Operator == null)
|
||||
return;
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
if (Operator == null)
|
||||
return;
|
||||
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Type rightSideType = RightPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue == null)
|
||||
return;
|
||||
|
||||
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
|
||||
UpdateRightSideStatic(null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// If the operator does not support a right side, immediately evaluate with null
|
||||
if (Operator.RightSideType == null)
|
||||
return Operator.InternalEvaluate(LeftPath.GetValue(), null);
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = LeftPath.GetValue();
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
|
||||
LeftPath?.Save();
|
||||
Entity.LeftPath = LeftPath?.Entity;
|
||||
RightPath?.Save();
|
||||
Entity.RightPath = RightPath?.Entity;
|
||||
|
||||
Entity.RightStaticValue = CoreJson.SerializeObject(RightStaticValue);
|
||||
|
||||
if (Operator?.Plugin != null)
|
||||
{
|
||||
Entity.OperatorPluginGuid = Operator.Plugin.Guid;
|
||||
Entity.OperatorType = Operator.GetType().Name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
if (Entity.OperatorPluginGuid == conditionOperator.Plugin!.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
if (e.Registration.ConditionOperator != Operator)
|
||||
return;
|
||||
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,257 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A condition that evaluates to true when an event is triggered
|
||||
/// </summary>
|
||||
public class DataModelConditionEvent : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
private bool _reinitializing;
|
||||
private IDataModelEvent? _valueChangedEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionEvent(DataModelConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionEventEntity();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the event property
|
||||
/// </summary>
|
||||
public DataModelPath? EventPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time the event this condition is applied to was triggered
|
||||
/// </summary>
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of argument the event provides
|
||||
/// </summary>
|
||||
public Type? EventArgumentType { get; private set; }
|
||||
|
||||
internal DataModelConditionEventEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||
|
||||
IDataModelEvent? dataModelEvent = GetDataModelEvent();
|
||||
if (dataModelEvent == null)
|
||||
return false;
|
||||
dataModelEvent.Update();
|
||||
|
||||
// Only evaluate to true once every time the event has been triggered since the last evaluation
|
||||
if (dataModelEvent.LastTrigger <= LastTrigger)
|
||||
return false;
|
||||
|
||||
LastTrigger = DateTime.Now;
|
||||
|
||||
// If there is a child (root group), it must evaluate to true whenever the event triggered
|
||||
if (Children.Any())
|
||||
return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped);
|
||||
|
||||
// If there are no children, we always evaluate to true whenever the event triggered
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event the condition is triggered by
|
||||
/// </summary>
|
||||
public void UpdateEvent(DataModelPath? path)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update event to an invalid path");
|
||||
|
||||
EventPath?.Dispose();
|
||||
EventPath = path != null ? new DataModelPath(path) : null;
|
||||
SubscribeToEventPath();
|
||||
CreateValueChangedEventIfNeeded();
|
||||
|
||||
// Remove the old root group that was tied to the old data model
|
||||
ClearChildren();
|
||||
|
||||
if (EventPath != null)
|
||||
{
|
||||
EventArgumentType = GetEventArgumentType();
|
||||
// Create a new root group
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
EventArgumentType = null;
|
||||
}
|
||||
|
||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by</returns>
|
||||
public IDataModelEvent? GetDataModelEvent()
|
||||
{
|
||||
if (_valueChangedEvent != null)
|
||||
return _valueChangedEvent;
|
||||
return EventPath?.GetValue() as IDataModelEvent;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
EventPath?.Dispose();
|
||||
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (EventPath != null && !EventPath.IsValid)
|
||||
return;
|
||||
|
||||
// Target list
|
||||
EventPath?.Save();
|
||||
Entity.EventPath = EventPath?.Entity;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
ClearChildren();
|
||||
|
||||
if (Entity.EventPath == null)
|
||||
return;
|
||||
|
||||
DataModelPath eventPath = new(null, Entity.EventPath);
|
||||
EventPath = eventPath;
|
||||
SubscribeToEventPath();
|
||||
CreateValueChangedEventIfNeeded();
|
||||
|
||||
EventArgumentType = GetEventArgumentType();
|
||||
// There should only be one child and it should be a group
|
||||
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
|
||||
{
|
||||
AddChild(new DataModelConditionGroup(this, rootGroup));
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
|
||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
||||
}
|
||||
|
||||
private Type? GetEventArgumentType()
|
||||
{
|
||||
if (EventPath == null || !EventPath.IsValid)
|
||||
return null;
|
||||
|
||||
if (_valueChangedEvent != null)
|
||||
return _valueChangedEvent.ArgumentsType;
|
||||
|
||||
// Cannot rely on EventPath.GetValue() because part of the path might be null
|
||||
Type eventType = EventPath.GetPropertyType()!;
|
||||
return eventType.IsGenericType ? eventType.GetGenericArguments()[0] : typeof(DataModelEventArgs);
|
||||
}
|
||||
|
||||
private void SubscribeToEventPath()
|
||||
{
|
||||
if (EventPath == null) return;
|
||||
EventPath.PathValidated += EventPathOnPathValidated;
|
||||
EventPath.PathInvalidated += EventPathOnPathInvalidated;
|
||||
}
|
||||
|
||||
private void CreateValueChangedEventIfNeeded()
|
||||
{
|
||||
Type? propertyType = EventPath?.GetPropertyType();
|
||||
if (propertyType == null)
|
||||
return;
|
||||
|
||||
if (!typeof(IDataModelEvent).IsAssignableFrom(propertyType))
|
||||
{
|
||||
IDataModelEvent? instance = (IDataModelEvent?) Activator.CreateInstance(typeof(DataModelValueChangedEvent<>).MakeGenericType(propertyType), EventPath);
|
||||
_valueChangedEvent = instance ?? throw new ArtemisCoreException("Failed to create a DataModelValueChangedEvent for a property changed data model event");
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueChangedEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void EventPathOnPathValidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
EventPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
private void EventPathOnPathInvalidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
EventPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate like evaluated inside a <see cref="DataModelConditionEvent" />
|
||||
/// </summary>
|
||||
public class DataModelConditionEventPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEventPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionEventPredicateEntity())
|
||||
{
|
||||
DataModelConditionEvent = null!;
|
||||
ApplyParentEvent();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
DataModelConditionEvent = null!;
|
||||
ApplyParentEvent();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model condition event this predicate belongs to
|
||||
/// </summary>
|
||||
public DataModelConditionEvent DataModelConditionEvent { get; private set; }
|
||||
|
||||
private void ApplyParentEvent()
|
||||
{
|
||||
DataModelConditionPart? current = Parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is DataModelConditionEvent parentEvent)
|
||||
{
|
||||
DataModelConditionEvent = parentEvent;
|
||||
return;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
if (DataModelConditionEvent == null)
|
||||
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event");
|
||||
}
|
||||
|
||||
private object? GetEventPathValue(DataModelPath path, object? target)
|
||||
{
|
||||
lock (path)
|
||||
{
|
||||
if (!(path.Target is EventPredicateWrapperDataModel wrapper))
|
||||
throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target");
|
||||
|
||||
wrapper.UntypedArguments = target;
|
||||
return path.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = DataModelConditionEvent.EventArgumentType != null
|
||||
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
{
|
||||
// Right side dynamic using event arguments
|
||||
if (Entity.RightPath.WrapperType == PathWrapperType.Event)
|
||||
{
|
||||
RightPath = DataModelConditionEvent.EventArgumentType != null
|
||||
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath)
|
||||
: null;
|
||||
}
|
||||
// Right side dynamic
|
||||
else
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <summary>
|
||||
/// Not supported for event predicates, always returns <c>false</c>
|
||||
/// </summary>
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// If the operator does not support a right side, immediately evaluate with null
|
||||
if (Operator.RightSideType == null)
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null);
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = GetEventPathValue(LeftPath, target);
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
// If the path targets a property inside the event, evaluate on the event path value instead of the right path value
|
||||
if (RightPath.Target is EventPredicateWrapperDataModel)
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target));
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||
/// static value
|
||||
/// </summary>
|
||||
public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGeneralPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = new DataModelPath(null, Entity.LeftPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,190 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A group containing zero to many <see cref="DataModelConditionPart" />s which it evaluates using a boolean specific
|
||||
/// operator
|
||||
/// </summary>
|
||||
public class DataModelConditionGroup : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionGroup(DataModelConditionPart? parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionGroupEntity();
|
||||
ChildAdded += OnChildrenChanged;
|
||||
ChildRemoved += OnChildrenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="entity"></param>
|
||||
public DataModelConditionGroup(DataModelConditionPart? parent, DataModelConditionGroupEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
|
||||
|
||||
foreach (DataModelConditionPartEntity childEntity in Entity.Children)
|
||||
{
|
||||
if (childEntity is DataModelConditionGroupEntity groupEntity)
|
||||
AddChild(new DataModelConditionGroup(this, groupEntity));
|
||||
else if (childEntity is DataModelConditionListEntity listEntity)
|
||||
AddChild(new DataModelConditionList(this, listEntity));
|
||||
else if (childEntity is DataModelConditionEventEntity eventEntity)
|
||||
AddChild(new DataModelConditionEvent(this, eventEntity));
|
||||
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
|
||||
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
|
||||
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
|
||||
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
|
||||
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
|
||||
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
|
||||
}
|
||||
|
||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||
ChildAdded += OnChildrenChanged;
|
||||
ChildRemoved += OnChildrenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the boolean operator of this group
|
||||
/// </summary>
|
||||
public BooleanOperator BooleanOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this group contains any events
|
||||
/// </summary>
|
||||
public bool ContainsEvents { get; private set; }
|
||||
|
||||
internal DataModelConditionGroupEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
||||
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].Evaluate();
|
||||
|
||||
if (ContainsEvents)
|
||||
{
|
||||
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
|
||||
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
|
||||
}
|
||||
return EvaluateWithOperator(Children);
|
||||
}
|
||||
|
||||
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
|
||||
{
|
||||
return BooleanOperator switch
|
||||
{
|
||||
BooleanOperator.And => targets.All(c => c.Evaluate()),
|
||||
BooleanOperator.Or => targets.Any(c => c.Evaluate()),
|
||||
BooleanOperator.AndNot => targets.All(c => !c.Evaluate()),
|
||||
BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
||||
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].EvaluateObject(target);
|
||||
|
||||
return BooleanOperator switch
|
||||
{
|
||||
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
|
||||
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
Entity.BooleanOperator = (int) BooleanOperator;
|
||||
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
private void OnChildrenChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a boolean operator
|
||||
/// </summary>
|
||||
public enum BooleanOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// All the conditions in the group should evaluate to true
|
||||
/// </summary>
|
||||
And,
|
||||
|
||||
/// <summary>
|
||||
/// Any of the conditions in the group should evaluate to true
|
||||
/// </summary>
|
||||
Or,
|
||||
|
||||
/// <summary>
|
||||
/// All the conditions in the group should evaluate to false
|
||||
/// </summary>
|
||||
AndNot,
|
||||
|
||||
/// <summary>
|
||||
/// Any of the conditions in the group should evaluate to false
|
||||
/// </summary>
|
||||
OrNot
|
||||
}
|
||||
}
|
||||
@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A condition that evaluates one or more predicates inside a list
|
||||
/// </summary>
|
||||
public class DataModelConditionList : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
private bool _reinitializing;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionList" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionList(DataModelConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionListEntity();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionList(DataModelConditionPart parent, DataModelConditionListEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list operator
|
||||
/// </summary>
|
||||
public ListOperator ListOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the list property
|
||||
/// </summary>
|
||||
public DataModelPath? ListPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content of the list this predicate is evaluated on
|
||||
/// </summary>
|
||||
public Type? ListType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the list contains primitives
|
||||
/// </summary>
|
||||
public bool IsPrimitiveList { get; set; }
|
||||
|
||||
internal DataModelConditionListEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (ListPath == null || !ListPath.IsValid)
|
||||
return false;
|
||||
|
||||
return EvaluateObject(ListPath.GetValue());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list the predicate is evaluated on
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the list inside the list</param>
|
||||
public void UpdateList(DataModelPath? path)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update list to an invalid path");
|
||||
|
||||
ListPath?.Dispose();
|
||||
ListPath = path != null ? new DataModelPath(path) : null;
|
||||
SubscribeToListPath();
|
||||
|
||||
// Remove the old root group that was tied to the old data model
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
|
||||
if (ListPath != null)
|
||||
{
|
||||
Type listType = ListPath.GetPropertyType()!;
|
||||
ListType = listType.GetGenericEnumerableType();
|
||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
||||
|
||||
// Create a new root group
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
ListPath?.Dispose();
|
||||
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (!Children.Any())
|
||||
return false;
|
||||
if (!(target is IEnumerable enumerable))
|
||||
return false;
|
||||
|
||||
IEnumerable<object> objectList = enumerable.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (ListPath != null && !ListPath.IsValid)
|
||||
return;
|
||||
|
||||
// Target list
|
||||
ListPath?.Save();
|
||||
Entity.ListPath = ListPath?.Entity;
|
||||
|
||||
// Operator
|
||||
Entity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
|
||||
if (Entity.ListPath == null)
|
||||
return;
|
||||
|
||||
// Ensure the list path is valid and points to a list
|
||||
DataModelPath listPath = new(null, Entity.ListPath);
|
||||
Type listType = listPath.GetPropertyType()!;
|
||||
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
|
||||
if (listPath.IsValid && !PointsToList(listPath))
|
||||
return;
|
||||
|
||||
ListPath = listPath;
|
||||
SubscribeToListPath();
|
||||
if (ListPath.IsValid)
|
||||
{
|
||||
ListType = listType.GetGenericEnumerableType();
|
||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
IsPrimitiveList = false;
|
||||
}
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
|
||||
{
|
||||
AddChild(new DataModelConditionGroup(this, rootGroup));
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
}
|
||||
|
||||
private bool PointsToList(DataModelPath dataModelPath)
|
||||
{
|
||||
Type? type = dataModelPath.GetPropertyType();
|
||||
return type?.IsGenericEnumerable() ?? false;
|
||||
}
|
||||
|
||||
private void SubscribeToListPath()
|
||||
{
|
||||
if (ListPath == null) return;
|
||||
ListPath.PathValidated += ListPathOnPathValidated;
|
||||
ListPath.PathInvalidated += ListPathOnPathInvalidated;
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void ListPathOnPathValidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
ListPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
private void ListPathOnPathInvalidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
ListPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a list operator
|
||||
/// </summary>
|
||||
public enum ListOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Any of the list items should evaluate to true
|
||||
/// </summary>
|
||||
Any,
|
||||
|
||||
/// <summary>
|
||||
/// All of the list items should evaluate to true
|
||||
/// </summary>
|
||||
All,
|
||||
|
||||
/// <summary>
|
||||
/// None of the list items should evaluate to true
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// A specific amount of the list items should evaluate to true
|
||||
/// </summary>
|
||||
Count
|
||||
}
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
|
||||
/// </summary>
|
||||
public class DataModelConditionListPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
|
||||
{
|
||||
DataModelConditionList = null!;
|
||||
ApplyParentList();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
DataModelConditionList = null!;
|
||||
ApplyParentList();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model condition list this predicate belongs to
|
||||
/// </summary>
|
||||
public DataModelConditionList DataModelConditionList { get; private set; }
|
||||
|
||||
private void ApplyParentList()
|
||||
{
|
||||
DataModelConditionPart? current = Parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is DataModelConditionList parentList)
|
||||
{
|
||||
DataModelConditionList = parentList;
|
||||
return;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
if (DataModelConditionList == null)
|
||||
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
|
||||
}
|
||||
|
||||
private object? GetListPathValue(DataModelPath path, object? target)
|
||||
{
|
||||
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
|
||||
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
|
||||
|
||||
wrapper.UntypedValue = target;
|
||||
return path.GetValue();
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = DataModelConditionList.ListType != null
|
||||
? new DataModelPath(ListPredicateWrapperDataModel.Create(
|
||||
DataModelConditionList.ListType,
|
||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
||||
), Entity.LeftPath)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
{
|
||||
// Right side dynamic inside the list
|
||||
if (Entity.RightPath.WrapperType == PathWrapperType.List)
|
||||
{
|
||||
RightPath = DataModelConditionList.ListType != null
|
||||
? new DataModelPath(ListPredicateWrapperDataModel.Create(
|
||||
DataModelConditionList.ListType,
|
||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
||||
), Entity.RightPath)
|
||||
: null;
|
||||
}
|
||||
// Right side dynamic
|
||||
else
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = DataModelConditionList.IsPrimitiveList
|
||||
? DataModelConditionList.ListType
|
||||
: LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <summary>
|
||||
/// Not supported for list predicates, always returns <c>false</c>
|
||||
/// </summary>
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = GetListPathValue(LeftPath, target);
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
// If the path targets a property inside the list, evaluate on the list path value instead of the right path value
|
||||
if (RightPath.Target is ListPredicateWrapperDataModel)
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EventPredicateWrapperDataModel<T> : EventPredicateWrapperDataModel
|
||||
{
|
||||
[DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")]
|
||||
public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a datamodel that wraps the event arguments of an event
|
||||
/// </summary>
|
||||
public abstract class EventPredicateWrapperDataModel : DataModel
|
||||
{
|
||||
internal EventPredicateWrapperDataModel()
|
||||
{
|
||||
Module = Constants.CorePluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last arguments of this event as an object
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public object? UntypedArguments { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EventPredicateWrapperDataModel" /> class
|
||||
/// </summary>
|
||||
public static EventPredicateWrapperDataModel Create(Type type)
|
||||
{
|
||||
object? instance = Activator.CreateInstance(typeof(EventPredicateWrapperDataModel<>).MakeGenericType(type));
|
||||
if (instance == null)
|
||||
throw new ArtemisCoreException($"Failed to create an instance of EventPredicateWrapperDataModel<T> for type {type.Name}");
|
||||
|
||||
return (EventPredicateWrapperDataModel) instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ListPredicateWrapperDataModel<T> : ListPredicateWrapperDataModel
|
||||
{
|
||||
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a datamodel that wraps a value in a list
|
||||
/// </summary>
|
||||
public abstract class ListPredicateWrapperDataModel : DataModel
|
||||
{
|
||||
internal ListPredicateWrapperDataModel()
|
||||
{
|
||||
Module = Constants.CorePluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this list as an object
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public object? UntypedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list item
|
||||
/// </summary>
|
||||
[DataModelIgnore]
|
||||
public string? ItemName { get; set; }
|
||||
|
||||
#region Overrides of DataModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ItemName))
|
||||
return new DataModelPropertyAttribute {Name = ItemName};
|
||||
return base.GetPropertyDescription(propertyInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel" /> class
|
||||
/// </summary>
|
||||
public static ListPredicateWrapperDataModel Create(Type type, string? name = null)
|
||||
{
|
||||
ListPredicateWrapperDataModel? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type)) as ListPredicateWrapperDataModel;
|
||||
if (instance == null)
|
||||
throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel<T> for type {type.Name}");
|
||||
|
||||
instance.ItemName = name;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,20 +17,20 @@ namespace Artemis.Core
|
||||
{
|
||||
LayerProperty = dataBindingRegistration.LayerProperty;
|
||||
Entity = new DataBindingEntity();
|
||||
Script = new NodeScript<TProperty>(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, "");
|
||||
|
||||
ApplyRegistration(dataBindingRegistration);
|
||||
Save();
|
||||
ApplyDataBindingMode();
|
||||
}
|
||||
|
||||
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
Entity = entity;
|
||||
Script = new NodeScript<TProperty>(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, "");
|
||||
|
||||
// Load will add children so be initialized before that
|
||||
Load();
|
||||
ApplyDataBindingMode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -48,10 +48,7 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding mode
|
||||
/// </summary>
|
||||
public IDataBindingMode<TLayerProperty, TProperty>? DataBindingMode { get; private set; }
|
||||
public NodeScript<TProperty> Script { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the easing time of the data binding
|
||||
@ -78,18 +75,18 @@ namespace Artemis.Core
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
if (Converter == null || DataBindingMode == null)
|
||||
if (Converter == null)
|
||||
return baseValue;
|
||||
|
||||
TProperty value = DataBindingMode.GetValue(baseValue);
|
||||
Script.Run();
|
||||
|
||||
// If no easing is to be applied simple return whatever the current value is
|
||||
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
|
||||
return value;
|
||||
return Script.Result;
|
||||
|
||||
// If the value changed, update the current and previous values used for easing
|
||||
if (!Equals(value, _currentValue))
|
||||
ResetEasing(value);
|
||||
if (!Equals(Script.Result, _currentValue))
|
||||
ResetEasing(Script.Result);
|
||||
|
||||
// Apply interpolation between the previous and current value
|
||||
return GetInterpolatedValue();
|
||||
@ -118,7 +115,9 @@ namespace Artemis.Core
|
||||
|
||||
if (Registration != null)
|
||||
Registration.DataBinding = null;
|
||||
DataBindingMode?.Dispose();
|
||||
|
||||
Script?.Dispose();
|
||||
Script = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,57 +220,6 @@ namespace Artemis.Core
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Mode management
|
||||
|
||||
/// <summary>
|
||||
/// Changes the data binding mode of the data binding to the specified <paramref name="dataBindingMode" />
|
||||
/// </summary>
|
||||
public void ChangeDataBindingMode(DataBindingModeType dataBindingMode)
|
||||
{
|
||||
switch (dataBindingMode)
|
||||
{
|
||||
case DataBindingModeType.Direct:
|
||||
Entity.DataBindingMode = new DirectDataBindingEntity();
|
||||
break;
|
||||
case DataBindingModeType.Conditional:
|
||||
Entity.DataBindingMode = new ConditionalDataBindingEntity();
|
||||
break;
|
||||
default:
|
||||
Entity.DataBindingMode = null;
|
||||
break;
|
||||
}
|
||||
|
||||
ApplyDataBindingMode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the current data binding mode with one based on the provided data binding mode entity
|
||||
/// </summary>
|
||||
/// <param name="dataBindingModeEntity">The data binding mode entity to base the new data binding mode upon</param>
|
||||
public void ApplyDataBindingEntity(IDataBindingModeEntity dataBindingModeEntity)
|
||||
{
|
||||
Entity.DataBindingMode = dataBindingModeEntity;
|
||||
ApplyDataBindingMode();
|
||||
}
|
||||
|
||||
private void ApplyDataBindingMode()
|
||||
{
|
||||
DataBindingMode?.Dispose();
|
||||
DataBindingMode = null;
|
||||
|
||||
switch (Entity.DataBindingMode)
|
||||
{
|
||||
case DirectDataBindingEntity directDataBindingEntity:
|
||||
DataBindingMode = new DirectDataBinding<TLayerProperty, TProperty>(this, directDataBindingEntity);
|
||||
break;
|
||||
case ConditionalDataBindingEntity conditionalDataBindingEntity:
|
||||
DataBindingMode = new ConditionalDataBinding<TLayerProperty, TProperty>(this, conditionalDataBindingEntity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -288,7 +236,10 @@ namespace Artemis.Core
|
||||
EasingTime = Entity.EasingTime;
|
||||
EasingFunction = (Easings.Functions) Entity.EasingFunction;
|
||||
|
||||
DataBindingMode?.Load();
|
||||
Script.Dispose();
|
||||
Script = Entity.NodeScript != null
|
||||
? new NodeScript<TProperty>(Entity.NodeScript)
|
||||
: new NodeScript<TProperty>(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -307,30 +258,10 @@ namespace Artemis.Core
|
||||
Entity.EasingTime = EasingTime;
|
||||
Entity.EasingFunction = (int) EasingFunction;
|
||||
|
||||
DataBindingMode?.Save();
|
||||
Script?.Save();
|
||||
Entity.NodeScript = Script?.Entity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mode that determines how the data binding is applied to the layer property
|
||||
/// </summary>
|
||||
public enum DataBindingModeType
|
||||
{
|
||||
/// <summary>
|
||||
/// Disables the data binding
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the layer property value with the data binding value
|
||||
/// </summary>
|
||||
Direct,
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the layer property value with the data binding value
|
||||
/// </summary>
|
||||
Conditional
|
||||
}
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data binding mode that applies a value depending on conditions
|
||||
/// </summary>
|
||||
public class ConditionalDataBinding<TLayerProperty, TProperty> : IDataBindingMode<TLayerProperty, TProperty>
|
||||
{
|
||||
private readonly List<DataBindingCondition<TLayerProperty, TProperty>> _conditions = new();
|
||||
private bool _disposed;
|
||||
|
||||
internal ConditionalDataBinding(DataBinding<TLayerProperty, TProperty> dataBinding, ConditionalDataBindingEntity entity)
|
||||
{
|
||||
DataBinding = dataBinding;
|
||||
Entity = entity;
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of conditions applied to this data binding
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions => _conditions.AsReadOnly();
|
||||
|
||||
internal ConditionalDataBindingEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TProperty GetValue(TProperty baseValue)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
||||
|
||||
DataBindingCondition<TLayerProperty, TProperty>? condition = Conditions.FirstOrDefault(c => c.Evaluate());
|
||||
return condition == null ? baseValue : condition.Value;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
foreach (DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition in Conditions)
|
||||
dataBindingCondition.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Values
|
||||
|
||||
/// <summary>
|
||||
/// Adds a condition to the conditional data binding's <see cref="Conditions" /> collection
|
||||
/// </summary>
|
||||
/// <returns>The newly created <see cref="DataBindingCondition{TLayerProperty,TProperty}" /></returns>
|
||||
public DataBindingCondition<TLayerProperty, TProperty> AddCondition()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
||||
|
||||
DataBindingCondition<TLayerProperty, TProperty> condition = new(this);
|
||||
_conditions.Add(condition);
|
||||
|
||||
ApplyOrder();
|
||||
OnConditionsUpdated();
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a condition from the conditional data binding's <see cref="Conditions" /> collection and disposes it
|
||||
/// </summary>
|
||||
/// <param name="condition"></param>
|
||||
public void RemoveCondition(DataBindingCondition<TLayerProperty, TProperty> condition)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
||||
if (!_conditions.Contains(condition))
|
||||
return;
|
||||
|
||||
_conditions.Remove(condition);
|
||||
condition.Dispose();
|
||||
|
||||
ApplyOrder();
|
||||
OnConditionsUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the current order of conditions to the <see cref="Conditions" /> collection
|
||||
/// </summary>
|
||||
public void ApplyOrder()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ConditionalDataBinding");
|
||||
|
||||
_conditions.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||
for (int index = 0; index < _conditions.Count; index++)
|
||||
{
|
||||
DataBindingCondition<TLayerProperty, TProperty> condition = _conditions[index];
|
||||
condition.Order = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
foreach (DataBindingConditionEntity dataBindingConditionEntity in Entity.Values)
|
||||
_conditions.Add(new DataBindingCondition<TLayerProperty, TProperty>(this, dataBindingConditionEntity));
|
||||
|
||||
ApplyOrder();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Entity.Values.Clear();
|
||||
foreach (DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition in Conditions)
|
||||
dataBindingCondition.Save();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a condition is added or removed
|
||||
/// </summary>
|
||||
public event EventHandler? ConditionsUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ConditionsUpdated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnConditionsUpdated()
|
||||
{
|
||||
ConditionsUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class DataBindingCondition<TLayerProperty, TProperty> : IDataBindingCondition
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataBindingCondition{TLayerProperty,TProperty}" /> class
|
||||
/// </summary>
|
||||
/// <param name="conditionalDataBinding">The conditional data binding this condition is applied too</param>
|
||||
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding)
|
||||
{
|
||||
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
|
||||
Order = conditionalDataBinding.Conditions.Count + 1;
|
||||
Condition = new DataModelConditionGroup(null);
|
||||
Value = default!;
|
||||
|
||||
Entity = new DataBindingConditionEntity();
|
||||
Save();
|
||||
}
|
||||
|
||||
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding, DataBindingConditionEntity entity)
|
||||
{
|
||||
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
|
||||
Entity = entity;
|
||||
Condition = null!;
|
||||
Value = default!;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conditional data binding this condition is applied to
|
||||
/// </summary>
|
||||
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position at which the modifier appears on the data binding
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value to be applied when the condition is met
|
||||
/// </summary>
|
||||
public TProperty Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root group of the condition that must be met
|
||||
/// </summary>
|
||||
public DataModelConditionGroup Condition { get; private set; }
|
||||
|
||||
internal DataBindingConditionEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Evaluate()
|
||||
{
|
||||
return Condition.Evaluate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBindingCondition");
|
||||
|
||||
if (!ConditionalDataBinding.Entity.Values.Contains(Entity))
|
||||
ConditionalDataBinding.Entity.Values.Add(Entity);
|
||||
|
||||
Entity.Condition = Condition.Entity;
|
||||
Condition.Save();
|
||||
|
||||
Entity.Value = CoreJson.SerializeObject(Value);
|
||||
Entity.Order = Order;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataBindingCondition");
|
||||
|
||||
Condition = Entity.Condition != null
|
||||
? new DataModelConditionGroup(null, Entity.Condition)
|
||||
: new DataModelConditionGroup(null);
|
||||
|
||||
Value = (Entity.Value == null ? default : CoreJson.DeserializeObject<TProperty>(Entity.Value))!;
|
||||
Order = Entity.Order;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
Condition.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition and a value inside a <see cref="ConditionalDataBinding{TLayerProperty,TProperty}" />
|
||||
/// </summary>
|
||||
public interface IDataBindingCondition : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the condition
|
||||
/// </summary>
|
||||
bool Evaluate();
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A modifier that changes the source value of a data binding in some way
|
||||
/// <para>
|
||||
/// To implement your own condition operator, inherit <see cref="DataBindingModifierType{TValue, TParameter}" /> or
|
||||
/// <see cref="DataBindingModifierType{TValue}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class BaseDataBindingModifierType
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the plugin this data binding modifier belongs to
|
||||
/// <para>Note: Not set until after registering</para>
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value type of this modifier type
|
||||
/// </summary>
|
||||
public abstract Type ValueType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter type of this modifier type. May be null if the modifier type does not support a parameter
|
||||
/// </summary>
|
||||
public abstract Type? ParameterType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this modifier
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon of this modifier
|
||||
/// </summary>
|
||||
public abstract string? Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of this modifier
|
||||
/// </summary>
|
||||
public virtual string? Description => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the category of this modifier
|
||||
/// </summary>
|
||||
public virtual string? Category => null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type is supported by the modifier
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
|
||||
/// <param name="part">Which part of the modifier to check, the value or the parameter</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the modifier to the provided current value
|
||||
/// <para>
|
||||
/// This leaves the caller responsible for the types matching <see cref="ValueType" /> and
|
||||
/// <see cref="ParameterType" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="currentValue">The current value before modification, type should match <see cref="ValueType" /></param>
|
||||
/// <param name="parameterValue">
|
||||
/// The parameter to use for the modification, type should match <see cref="ParameterType" />
|
||||
/// </param>
|
||||
/// <returns>The modified value, with a type of <see cref="ValueType" /></returns>
|
||||
internal abstract object? InternalApply(object? currentValue, object? parameterValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a part of a modifier type
|
||||
/// </summary>
|
||||
public enum ModifierTypePart
|
||||
{
|
||||
/// <summary>
|
||||
/// The value part of a modifier, backed by <see cref="DataBindingModifierType{TValue}.ValueType" />
|
||||
/// </summary>
|
||||
Value,
|
||||
|
||||
/// <summary>
|
||||
/// The parameter part of a modifier, backed by <see cref="DataBindingModifierType{TValue,TParameter}.ParameterType" />
|
||||
/// </summary>
|
||||
Parameter
|
||||
}
|
||||
}
|
||||
@ -1,324 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class DataBindingModifier<TLayerProperty, TProperty> : IDataBindingModifier
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
internal DataBindingModifier(DirectDataBinding<TLayerProperty, TProperty> 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<TLayerProperty, TProperty> directDataBinding, DataBindingModifierEntity entity)
|
||||
{
|
||||
DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding));
|
||||
Entity = entity;
|
||||
Load();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of modifier that is being applied
|
||||
/// </summary>
|
||||
public BaseDataBindingModifierType? ModifierType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the direct data binding this modifier is applied to
|
||||
/// </summary>
|
||||
public DirectDataBinding<TLayerProperty, TProperty> DirectDataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the parameter, can either be dynamic (based on a data model value) or static
|
||||
/// </summary>
|
||||
public ProfileRightSideType ParameterType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position at which the modifier appears on the data binding
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the parameter property
|
||||
/// </summary>
|
||||
public DataModelPath? ParameterPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter static value, only used it <see cref="ParameterType" /> is
|
||||
/// <see cref="ProfileRightSideType.Static" />
|
||||
/// </summary>
|
||||
public object? ParameterStaticValue { get; private set; }
|
||||
|
||||
internal DataBindingModifierEntity Entity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the modifier to the provided value
|
||||
/// </summary>
|
||||
/// <param name="currentValue">The value to apply the modifier to, should be of the same type as the data binding target</param>
|
||||
/// <returns>The modified value</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the modifier type of the modifier and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="modifierType"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the parameter of the modifier and makes the modifier dynamic
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the parameter</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="staticValue">The static value to use as a parameter</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded;
|
||||
DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
|
||||
|
||||
ParameterPath?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A modifier that changes the source value of a data binding in some way using a parameter
|
||||
/// </summary>
|
||||
public abstract class DataBindingModifierType<TValue, TParameter> : BaseDataBindingModifierType
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type ValueType => typeof(TValue);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type ParameterType => typeof(TParameter);
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the modifier must apply to a specific value
|
||||
/// </summary>
|
||||
/// <param name="currentValue">
|
||||
/// The current value before modification
|
||||
/// </param>
|
||||
/// <param name="parameterValue">
|
||||
/// The parameter to use for the modification
|
||||
/// </param>
|
||||
/// <returns>The modified value></returns>
|
||||
public abstract TValue Apply(TValue currentValue, TParameter parameterValue);
|
||||
|
||||
/// <inheritdoc />
|
||||
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!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A modifier that changes the source value of a data binding in some way
|
||||
/// </summary>
|
||||
public abstract class DataBindingModifierType<TValue> : BaseDataBindingModifierType
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type ValueType => typeof(TValue);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? ParameterType => null;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the modifier must apply to a specific value
|
||||
/// </summary>
|
||||
/// <param name="currentValue">
|
||||
/// The current value before modification
|
||||
/// </param>
|
||||
/// <returns>The modified value</returns>
|
||||
public abstract TValue Apply(TValue currentValue);
|
||||
|
||||
/// <inheritdoc />
|
||||
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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Modifies a data model value in a way defined by the modifier type
|
||||
/// </summary>
|
||||
public interface IDataBindingModifier : IStorageModel, IDisposable
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,217 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data binding mode that directly applies a data model value to a data binding
|
||||
/// </summary>
|
||||
public class DirectDataBinding<TLayerProperty, TProperty> : IDataBindingMode<TLayerProperty, TProperty>
|
||||
{
|
||||
private readonly List<DataBindingModifier<TLayerProperty, TProperty>> _modifiers = new();
|
||||
private bool _disposed;
|
||||
|
||||
internal DirectDataBinding(DataBinding<TLayerProperty, TProperty> dataBinding, DirectDataBindingEntity entity)
|
||||
{
|
||||
DataBinding = dataBinding;
|
||||
Entity = entity;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the source property
|
||||
/// </summary>
|
||||
public DataModelPath? SourcePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of modifiers applied to this data binding
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly();
|
||||
|
||||
internal DirectDataBindingEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
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<TLayerProperty, TProperty> dataBindingModifier in Modifiers)
|
||||
dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
|
||||
|
||||
return DataBinding.Converter.ConvertFromObject(dataBindingValue);
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
foreach (DataBindingModifier<TLayerProperty, TProperty> dataBindingModifier in Modifiers)
|
||||
dataBindingModifier.Dispose();
|
||||
|
||||
SourcePath?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
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<TLayerProperty, TProperty>(this, dataBindingModifierEntity));
|
||||
|
||||
ApplyOrder();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<TLayerProperty, TProperty> dataBindingModifier in Modifiers)
|
||||
dataBindingModifier.Save();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Source
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of the source property of this data binding
|
||||
/// </summary>
|
||||
public Type? GetSourceType()
|
||||
{
|
||||
return SourcePath?.GetPropertyType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the source of the data binding
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the source</param>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the direct data binding's <see cref="Modifiers" /> collection
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the parameter, can either be dynamic (based on a data model value) or static</param>
|
||||
public DataBindingModifier<TLayerProperty, TProperty> AddModifier(ProfileRightSideType type)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DirectDataBinding");
|
||||
|
||||
DataBindingModifier<TLayerProperty, TProperty> modifier = new(this, type);
|
||||
_modifiers.Add(modifier);
|
||||
|
||||
ApplyOrder();
|
||||
OnModifiersUpdated();
|
||||
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the direct data binding's <see cref="Modifiers" /> collection and disposes it
|
||||
/// </summary>
|
||||
public void RemoveModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DirectDataBinding");
|
||||
if (!_modifiers.Contains(modifier))
|
||||
return;
|
||||
|
||||
_modifiers.Remove(modifier);
|
||||
modifier.Dispose();
|
||||
|
||||
ApplyOrder();
|
||||
OnModifiersUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the current order of conditions to the <see cref="Modifiers" /> collection
|
||||
/// </summary>
|
||||
public void ApplyOrder()
|
||||
{
|
||||
_modifiers.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||
for (int index = 0; index < _modifiers.Count; index++)
|
||||
{
|
||||
DataBindingModifier<TLayerProperty, TProperty> modifier = _modifiers[index];
|
||||
modifier.Order = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a modifier is added or removed
|
||||
/// </summary>
|
||||
public event EventHandler? ModifiersUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ModifiersUpdated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnModifiersUpdated()
|
||||
{
|
||||
ModifiersUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data binding mode
|
||||
/// </summary>
|
||||
public interface IDataBindingMode<TLayerProperty, TProperty> : IStorageModel, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data binding this mode is applied to
|
||||
/// </summary>
|
||||
DataBinding<TLayerProperty, TProperty> DataBinding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the data binding
|
||||
/// </summary>
|
||||
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
|
||||
/// <returns></returns>
|
||||
TProperty GetValue(TProperty baseValue);
|
||||
}
|
||||
}
|
||||
@ -363,13 +363,6 @@ namespace Artemis.Core
|
||||
|
||||
Entity.Path = Path;
|
||||
Entity.DataModelId = DataModelId;
|
||||
|
||||
Entity.WrapperType = Target switch
|
||||
{
|
||||
ListPredicateWrapperDataModel _ => PathWrapperType.List,
|
||||
EventPredicateWrapperDataModel _ => PathWrapperType.Event,
|
||||
_ => PathWrapperType.None
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -7,7 +7,6 @@ using Artemis.Core.LayerEffects.Placeholder;
|
||||
using Artemis.Core.Properties;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
@ -68,11 +67,7 @@ namespace Artemis.Core
|
||||
internal void LoadRenderElement()
|
||||
{
|
||||
DisplayCondition = RenderElementEntity.NodeScript != null ? new NodeScript<bool>(RenderElementEntity.NodeScript) : null;
|
||||
|
||||
// DisplayCondition = RenderElementEntity.DisplayCondition != null
|
||||
// ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
|
||||
// : new DataModelConditionGroup(null);
|
||||
|
||||
|
||||
Timeline = RenderElementEntity.Timeline != null
|
||||
? new Timeline(RenderElementEntity.Timeline)
|
||||
: new Timeline();
|
||||
|
||||
@ -130,7 +130,7 @@ namespace Artemis.Core
|
||||
/// Gets the data model condition that must evaluate to <see langword="true" /> for this profile to be activated
|
||||
/// alongside any activation requirements of the <see cref="Module" />, if set
|
||||
/// </summary>
|
||||
public DataModelConditionGroup? ActivationCondition { get; set; }
|
||||
public NodeScript<bool>? ActivationCondition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the module this profile uses
|
||||
@ -168,7 +168,13 @@ namespace Artemis.Core
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
|
||||
ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate();
|
||||
if (ActivationCondition == null)
|
||||
ActivationConditionMet = true;
|
||||
else
|
||||
{
|
||||
ActivationCondition.Run();
|
||||
ActivationConditionMet = ActivationCondition.Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -231,7 +237,8 @@ namespace Artemis.Core
|
||||
|
||||
Icon.Load();
|
||||
|
||||
ActivationCondition = Entity.ActivationCondition != null ? new DataModelConditionGroup(null, Entity.ActivationCondition) : null;
|
||||
ActivationCondition?.Dispose();
|
||||
ActivationCondition = Entity.ActivationCondition != null ? new NodeScript<bool>(Entity.ActivationCondition) : null;
|
||||
|
||||
EnableHotkey = Entity.EnableHotkey != null ? new ProfileConfigurationHotkey(Entity.EnableHotkey) : null;
|
||||
DisableHotkey = Entity.DisableHotkey != null ? new ProfileConfigurationHotkey(Entity.DisableHotkey) : null;
|
||||
|
||||
@ -210,8 +210,6 @@ namespace Artemis.Core.Services
|
||||
Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
|
||||
_logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion);
|
||||
|
||||
DeserializationLogger.Initialize(Kernel!);
|
||||
|
||||
// Initialize the services
|
||||
_pluginManagementService.CopyBuiltInPlugins();
|
||||
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated);
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
internal class ConditionOperatorService : IConditionOperatorService
|
||||
{
|
||||
public ConditionOperatorService()
|
||||
{
|
||||
RegisterBuiltInConditionOperators();
|
||||
}
|
||||
|
||||
public ConditionOperatorRegistration RegisterConditionOperator(Plugin plugin, BaseConditionOperator conditionOperator)
|
||||
{
|
||||
if (plugin == null)
|
||||
throw new ArgumentNullException(nameof(plugin));
|
||||
if (conditionOperator == null)
|
||||
throw new ArgumentNullException(nameof(conditionOperator));
|
||||
|
||||
conditionOperator.Plugin = plugin;
|
||||
return ConditionOperatorStore.Add(conditionOperator);
|
||||
}
|
||||
|
||||
public void RemoveConditionOperator(ConditionOperatorRegistration registration)
|
||||
{
|
||||
if (registration == null)
|
||||
throw new ArgumentNullException(nameof(registration));
|
||||
ConditionOperatorStore.Remove(registration);
|
||||
}
|
||||
|
||||
public List<BaseConditionOperator> GetConditionOperatorsForType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
return ConditionOperatorStore.GetForType(type, side).Select(r => r.ConditionOperator).ToList();
|
||||
}
|
||||
|
||||
public BaseConditionOperator? GetConditionOperator(Guid operatorPluginGuid, string operatorType)
|
||||
{
|
||||
return ConditionOperatorStore.Get(operatorPluginGuid, operatorType)?.ConditionOperator;
|
||||
}
|
||||
|
||||
private void RegisterBuiltInConditionOperators()
|
||||
{
|
||||
// General usage for any type
|
||||
RegisterConditionOperator(Constants.CorePlugin, new EqualsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new NotEqualConditionOperator());
|
||||
|
||||
// Numeric operators
|
||||
RegisterConditionOperator(Constants.CorePlugin, new NumberEqualsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new NumberNotEqualConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new LessThanConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new GreaterThanConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new LessThanOrEqualConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new GreaterThanOrEqualConditionOperator());
|
||||
|
||||
// String operators
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringEqualsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringNotEqualConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringContainsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringNotContainsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringStartsWithConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringEndsWithConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringMatchesRegexConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringNullConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new StringNotNullConditionOperator());
|
||||
|
||||
// Enum operators
|
||||
RegisterConditionOperator(Constants.CorePlugin, new EnumContainsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new EnumNotContainsConditionOperator());
|
||||
|
||||
// Null checks, at the bottom
|
||||
// TODO: Implement a priority mechanism
|
||||
RegisterConditionOperator(Constants.CorePlugin, new NullConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePlugin, new NotNullConditionOperator());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
internal class DataBindingService : IDataBindingService
|
||||
{
|
||||
public DataBindingService()
|
||||
{
|
||||
RegisterBuiltInModifiers();
|
||||
}
|
||||
|
||||
public DataBindingModifierTypeRegistration RegisterModifierType(Plugin plugin, BaseDataBindingModifierType dataBindingModifierType)
|
||||
{
|
||||
if (plugin == null)
|
||||
throw new ArgumentNullException(nameof(plugin));
|
||||
if (dataBindingModifierType == null)
|
||||
throw new ArgumentNullException(nameof(dataBindingModifierType));
|
||||
|
||||
dataBindingModifierType.Plugin = plugin;
|
||||
return DataBindingModifierTypeStore.Add(dataBindingModifierType);
|
||||
}
|
||||
|
||||
public void RemoveModifierType(DataBindingModifierTypeRegistration registration)
|
||||
{
|
||||
if (registration == null)
|
||||
throw new ArgumentNullException(nameof(registration));
|
||||
DataBindingModifierTypeStore.Remove(registration);
|
||||
}
|
||||
|
||||
public List<BaseDataBindingModifierType> GetCompatibleModifierTypes(Type type, ModifierTypePart part)
|
||||
{
|
||||
return DataBindingModifierTypeStore.GetForType(type, part).Select(r => r.DataBindingModifierType).ToList();
|
||||
}
|
||||
|
||||
public BaseDataBindingModifierType? GetModifierType(Guid modifierTypePluginGuid, string modifierType)
|
||||
{
|
||||
return DataBindingModifierTypeStore.Get(modifierTypePluginGuid, modifierType)?.DataBindingModifierType;
|
||||
}
|
||||
|
||||
private void RegisterBuiltInModifiers()
|
||||
{
|
||||
// Numbers - General
|
||||
RegisterModifierType(Constants.CorePlugin, new SumModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SubtractModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new MultiplicationModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new DivideModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new PercentageOfModifierType());
|
||||
|
||||
// Numbers - Advanced
|
||||
RegisterModifierType(Constants.CorePlugin, new MaxModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new MinModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new ModuloModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new AbsoluteModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new PowerModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SquareRootModifierType());
|
||||
|
||||
// Numbers - Rounding
|
||||
RegisterModifierType(Constants.CorePlugin, new FloorModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new RoundModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new CeilingModifierType());
|
||||
|
||||
// Numbers - Trigonometric
|
||||
RegisterModifierType(Constants.CorePlugin, new SineModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new CosineModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new TangentModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new CotangentModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SecantModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new CosecantModifierType());
|
||||
|
||||
// Colors
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorSumModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorSaturateModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorDesaturateModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorBrightenModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorDarkenModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorRotateHueModifierType());
|
||||
RegisterModifierType(Constants.CorePlugin, new SKColorInvertModifierType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.Properties;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that allows you to register and retrieve conditions operators used by display conditions
|
||||
/// </summary>
|
||||
public interface IConditionOperatorService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a new condition operator for use in layer conditions
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin this condition operator belongs to</param>
|
||||
/// <param name="conditionOperator">The condition operator to register</param>
|
||||
ConditionOperatorRegistration RegisterConditionOperator([NotNull] Plugin plugin, [NotNull] BaseConditionOperator conditionOperator);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a condition operator so it is no longer available for use in layer conditions
|
||||
/// </summary>
|
||||
/// <param name="registration">The registration of the condition operator to remove</param>
|
||||
void RemoveConditionOperator([NotNull] ConditionOperatorRegistration registration);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the condition operators compatible with the provided type
|
||||
/// </summary>
|
||||
List<BaseConditionOperator> GetConditionOperatorsForType(Type type, ConditionParameterSide side);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a condition operator by its plugin GUID and type name
|
||||
/// </summary>
|
||||
/// <param name="operatorPluginGuid">The operator's plugin GUID</param>
|
||||
/// <param name="operatorType">The type name of the operator</param>
|
||||
BaseConditionOperator? GetConditionOperator(Guid operatorPluginGuid, string operatorType);
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.Properties;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that allows you to register and retrieve data binding modifiers used by the data bindings system
|
||||
/// </summary>
|
||||
public interface IDataBindingService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a new modifier type for use in data bindings
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin this modifier type belongs to</param>
|
||||
/// <param name="dataBindingModifierType">The modifier type to register</param>
|
||||
DataBindingModifierTypeRegistration RegisterModifierType([NotNull] Plugin plugin, [NotNull] BaseDataBindingModifierType dataBindingModifierType);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier type so it is no longer available for use in data bindings
|
||||
/// </summary>
|
||||
/// <param name="dataBindingModifierType">The registration of the modifier type to remove</param>
|
||||
void RemoveModifierType([NotNull] DataBindingModifierTypeRegistration dataBindingModifierType);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the data binding modifier types compatible with the provided type
|
||||
/// </summary>
|
||||
List<BaseDataBindingModifierType> GetCompatibleModifierTypes(Type type, ModifierTypePart part);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier type by its plugin GUID and type name
|
||||
/// </summary>
|
||||
/// <param name="modifierTypePluginGuid">The modifier type's plugin GUID</param>
|
||||
/// <param name="modifierType">The type name of the modifier type</param>
|
||||
/// <returns></returns>
|
||||
BaseDataBindingModifierType? GetModifierType(Guid modifierTypePluginGuid, string modifierType);
|
||||
}
|
||||
}
|
||||
@ -29,9 +29,6 @@ namespace Artemis.Core.Services
|
||||
|
||||
public ProfileService(ILogger logger,
|
||||
IRgbService rgbService,
|
||||
// TODO: Move these two
|
||||
IConditionOperatorService conditionOperatorService,
|
||||
IDataBindingService dataBindingService,
|
||||
IProfileCategoryRepository profileCategoryRepository,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IInputService inputService,
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ConditionOperatorStore
|
||||
{
|
||||
private static readonly List<ConditionOperatorRegistration> Registrations = new();
|
||||
|
||||
public static ConditionOperatorRegistration Add(BaseConditionOperator conditionOperator)
|
||||
{
|
||||
if (conditionOperator.Plugin == null)
|
||||
throw new ArtemisCoreException("Cannot add a condition operator to the store that is not related to a plugin");
|
||||
|
||||
ConditionOperatorRegistration registration;
|
||||
lock (Registrations)
|
||||
{
|
||||
if (Registrations.Any(r => r.ConditionOperator == conditionOperator))
|
||||
throw new ArtemisCoreException($"Condition operator store store already contains operator '{conditionOperator.Description}'");
|
||||
|
||||
registration = new ConditionOperatorRegistration(conditionOperator, conditionOperator.Plugin) {IsInStore = true};
|
||||
Registrations.Add(registration);
|
||||
}
|
||||
|
||||
OnConditionOperatorAdded(new ConditionOperatorStoreEvent(registration));
|
||||
return registration;
|
||||
}
|
||||
|
||||
public static void Remove(ConditionOperatorRegistration registration)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
if (!Registrations.Contains(registration))
|
||||
throw new ArtemisCoreException($"Condition operator store does not contain operator '{registration.ConditionOperator.Description}'");
|
||||
|
||||
Registrations.Remove(registration);
|
||||
registration.IsInStore = false;
|
||||
}
|
||||
|
||||
OnConditionOperatorRemoved(new ConditionOperatorStoreEvent(registration));
|
||||
}
|
||||
|
||||
public static ConditionOperatorRegistration? Get(Guid pluginGuid, string type)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
return Registrations.FirstOrDefault(r => r.Plugin.Guid == pluginGuid && r.ConditionOperator.GetType().Name == type);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ConditionOperatorRegistration> GetForType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
if (type == null)
|
||||
return new List<ConditionOperatorRegistration>(Registrations);
|
||||
|
||||
List<ConditionOperatorRegistration> candidates = Registrations.Where(r => r.ConditionOperator.SupportsType(type, side)).ToList();
|
||||
|
||||
// If there are multiple operators with the same description, use the closest match
|
||||
foreach (IGrouping<string, ConditionOperatorRegistration> candidate in candidates.GroupBy(r => r.ConditionOperator.Description).Where(g => g.Count() > 1).ToList())
|
||||
{
|
||||
ConditionOperatorRegistration closest = side == ConditionParameterSide.Left
|
||||
? candidate.OrderByDescending(r => r.ConditionOperator.LeftSideType.ScoreCastability(type)).First()
|
||||
: candidate.OrderByDescending(r => r.ConditionOperator.RightSideType!.ScoreCastability(type)).First();
|
||||
foreach (ConditionOperatorRegistration conditionOperator in candidate)
|
||||
{
|
||||
if (conditionOperator != closest)
|
||||
candidates.Remove(conditionOperator);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public static event EventHandler<ConditionOperatorStoreEvent>? ConditionOperatorAdded;
|
||||
public static event EventHandler<ConditionOperatorStoreEvent>? ConditionOperatorRemoved;
|
||||
|
||||
private static void OnConditionOperatorAdded(ConditionOperatorStoreEvent e)
|
||||
{
|
||||
ConditionOperatorAdded?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private static void OnConditionOperatorRemoved(ConditionOperatorStoreEvent e)
|
||||
{
|
||||
ConditionOperatorRemoved?.Invoke(null, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DataBindingModifierTypeStore
|
||||
{
|
||||
private static readonly List<DataBindingModifierTypeRegistration> Registrations = new();
|
||||
|
||||
public static DataBindingModifierTypeRegistration Add(BaseDataBindingModifierType modifierType)
|
||||
{
|
||||
if (modifierType.Plugin == null)
|
||||
throw new ArtemisCoreException("Cannot add a data binding modifier type that is not associated with a plugin");
|
||||
|
||||
DataBindingModifierTypeRegistration typeRegistration;
|
||||
lock (Registrations)
|
||||
{
|
||||
if (Registrations.Any(r => r.DataBindingModifierType == modifierType))
|
||||
throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{modifierType.Name}'");
|
||||
|
||||
typeRegistration = new DataBindingModifierTypeRegistration(modifierType, modifierType.Plugin) { IsInStore = true };
|
||||
Registrations.Add(typeRegistration);
|
||||
}
|
||||
|
||||
OnDataBindingModifierAdded(new DataBindingModifierTypeStoreEvent(typeRegistration));
|
||||
return typeRegistration;
|
||||
}
|
||||
|
||||
public static void Remove(DataBindingModifierTypeRegistration typeRegistration)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
if (!Registrations.Contains(typeRegistration))
|
||||
throw new ArtemisCoreException($"Data binding modifier type store does not contain modifier type '{typeRegistration.DataBindingModifierType.Name}'");
|
||||
|
||||
Registrations.Remove(typeRegistration);
|
||||
typeRegistration.IsInStore = false;
|
||||
}
|
||||
|
||||
OnDataBindingModifierRemoved(new DataBindingModifierTypeStoreEvent(typeRegistration));
|
||||
}
|
||||
|
||||
public static DataBindingModifierTypeRegistration? Get(Guid pluginGuid, string type)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
return Registrations.FirstOrDefault(r => r.Plugin.Guid == pluginGuid && r.DataBindingModifierType.GetType().Name == type);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DataBindingModifierTypeRegistration> GetForType(Type type, ModifierTypePart part)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
if (type == null)
|
||||
return new List<DataBindingModifierTypeRegistration>(Registrations);
|
||||
|
||||
List<DataBindingModifierTypeRegistration> candidates = Registrations.Where(r => r.DataBindingModifierType.SupportsType(type, part)).ToList();
|
||||
|
||||
// If there are multiple modifiers with the same description, use the closest match
|
||||
foreach (IGrouping<string, DataBindingModifierTypeRegistration> displayDataBindingModifiers in candidates.GroupBy(r => r.DataBindingModifierType.Name).Where(g => g.Count() > 1).ToList())
|
||||
{
|
||||
DataBindingModifierTypeRegistration closest = part == ModifierTypePart.Value
|
||||
? displayDataBindingModifiers.OrderByDescending(r => r.DataBindingModifierType.ValueType.ScoreCastability(type)).First()
|
||||
: displayDataBindingModifiers.OrderByDescending(r => r.DataBindingModifierType.ParameterType!.ScoreCastability(type)).First();
|
||||
foreach (DataBindingModifierTypeRegistration displayDataBindingModifier in displayDataBindingModifiers)
|
||||
{
|
||||
if (displayDataBindingModifier != closest)
|
||||
candidates.Remove(displayDataBindingModifier);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public static event EventHandler<DataBindingModifierTypeStoreEvent>? DataBindingModifierAdded;
|
||||
public static event EventHandler<DataBindingModifierTypeStoreEvent>? DataBindingModifierRemoved;
|
||||
|
||||
private static void OnDataBindingModifierAdded(DataBindingModifierTypeStoreEvent e)
|
||||
{
|
||||
DataBindingModifierAdded?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private static void OnDataBindingModifierRemoved(DataBindingModifierTypeStoreEvent e)
|
||||
{
|
||||
DataBindingModifierRemoved?.Invoke(null, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data model registration
|
||||
/// </summary>
|
||||
public class ConditionOperatorRegistration
|
||||
{
|
||||
internal ConditionOperatorRegistration(BaseConditionOperator conditionOperator, Plugin plugin)
|
||||
{
|
||||
ConditionOperator = conditionOperator;
|
||||
Plugin = plugin;
|
||||
|
||||
Plugin.Disabled += OnDisabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the condition operator that has been registered
|
||||
/// </summary>
|
||||
public BaseConditionOperator ConditionOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the condition operator is associated with
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the registration is in the internal Core store
|
||||
/// </summary>
|
||||
public bool IsInStore { get; internal set; }
|
||||
|
||||
private void OnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
Plugin.Disabled -= OnDisabled;
|
||||
if (IsInStore)
|
||||
ConditionOperatorStore.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data model registration
|
||||
/// </summary>
|
||||
public class DataBindingModifierTypeRegistration
|
||||
{
|
||||
internal DataBindingModifierTypeRegistration(BaseDataBindingModifierType dataBindingModifierType, Plugin plugin)
|
||||
{
|
||||
DataBindingModifierType = dataBindingModifierType;
|
||||
Plugin = plugin;
|
||||
|
||||
Plugin.Disabled += OnDisabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding modifier that has been registered
|
||||
/// </summary>
|
||||
public BaseDataBindingModifierType DataBindingModifierType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the data binding modifier is associated with
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the registration is in the internal Core store
|
||||
/// </summary>
|
||||
public bool IsInStore { get; internal set; }
|
||||
|
||||
private void OnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
Plugin.Disabled -= OnDisabled;
|
||||
if (IsInStore)
|
||||
DataBindingModifierTypeStore.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal static class DeserializationLogger
|
||||
{
|
||||
private static ILogger? _logger;
|
||||
|
||||
public static void Initialize(IKernel kernel)
|
||||
{
|
||||
_logger = kernel.Get<ILogger>();
|
||||
}
|
||||
|
||||
public static void LogPredicateDeserializationFailure(DataModelConditionPredicate dataModelConditionPredicate, JsonException exception)
|
||||
{
|
||||
_logger?.Warning(
|
||||
exception,
|
||||
"Failed to deserialize display condition predicate {left} {operator} {right}",
|
||||
dataModelConditionPredicate.Entity.LeftPath?.Path,
|
||||
dataModelConditionPredicate.Entity.OperatorType,
|
||||
dataModelConditionPredicate.Entity.RightPath?.Path
|
||||
);
|
||||
}
|
||||
|
||||
public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception)
|
||||
{
|
||||
_logger?.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public class ConditionalDataBindingEntity : IDataBindingModeEntity
|
||||
{
|
||||
public ConditionalDataBindingEntity()
|
||||
{
|
||||
Values = new List<DataBindingConditionEntity>();
|
||||
}
|
||||
|
||||
public List<DataBindingConditionEntity> Values { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public class DataBindingConditionEntity
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public DataModelConditionGroupEntity Condition { get; set; }
|
||||
public int Order { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Nodes;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
@ -7,7 +8,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
public string Identifier { get; set; }
|
||||
public TimeSpan EasingTime { get; set; }
|
||||
public int EasingFunction { get; set; }
|
||||
|
||||
public IDataBindingModeEntity DataBindingMode { get; set; }
|
||||
|
||||
public NodeScriptEntity NodeScript { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public class DataBindingModifierEntity
|
||||
{
|
||||
public string ModifierType { get; set; }
|
||||
public Guid? ModifierTypePluginGuid { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
public int ParameterType { get; set; }
|
||||
|
||||
public DataModelPathEntity ParameterPath { get; set; }
|
||||
|
||||
// Stored as a string to be able to control serialization and deserialization ourselves
|
||||
public string ParameterStaticValue { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public class DirectDataBindingEntity : IDataBindingModeEntity
|
||||
{
|
||||
public DirectDataBindingEntity()
|
||||
{
|
||||
Modifiers = new List<DataBindingModifierEntity>();
|
||||
}
|
||||
|
||||
public DataModelPathEntity SourcePath { get; set; }
|
||||
public List<DataBindingModifierEntity> Modifiers { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Artemis.Storage.Entities.Profile.DataBindings
|
||||
{
|
||||
public interface IDataBindingModeEntity
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -6,14 +6,5 @@ namespace Artemis.Storage.Entities.Profile
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string DataModelId { get; set; }
|
||||
|
||||
public PathWrapperType WrapperType { get; set; }
|
||||
}
|
||||
|
||||
public enum PathWrapperType
|
||||
{
|
||||
None,
|
||||
List,
|
||||
Event
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
using Artemis.Storage.Entities.Profile.Nodes;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile
|
||||
{
|
||||
@ -13,7 +14,7 @@ namespace Artemis.Storage.Entities.Profile
|
||||
|
||||
public bool IsSuspended { get; set; }
|
||||
public int ActivationBehaviour { get; set; }
|
||||
public DataModelConditionGroupEntity ActivationCondition { get; set; }
|
||||
public NodeScriptEntity ActivationCondition { get; set; }
|
||||
|
||||
public int HotkeyMode { get; set; }
|
||||
public ProfileConfigurationHotkeyEntity EnableHotkey { get; set; }
|
||||
|
||||
@ -77,4 +77,7 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,4 +1,5 @@
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Controls;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Ninject;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
@ -23,9 +24,9 @@ namespace Artemis.UI.Shared
|
||||
if (Initialized)
|
||||
return;
|
||||
|
||||
IColorPickerService colorPickerService = kernel.Get<IColorPickerService>();
|
||||
GradientPicker.ColorPickerService = colorPickerService;
|
||||
ColorPicker.ColorPickerService = colorPickerService;
|
||||
GradientPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
||||
ColorPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
||||
DataModelPicker.DataModelUIService = kernel.Get<IDataModelUIService>();
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
75
src/Artemis.UI.Shared/Controls/DataModelPicker.xaml
Normal file
75
src/Artemis.UI.Shared/Controls/DataModelPicker.xaml
Normal file
@ -0,0 +1,75 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.Controls.DataModelPicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Shared.Controls"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||
<ResourceDictionary>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<shared:BindingProxy x:Key="DataContextProxy" Data="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
||||
<DataTemplate x:Key="DataModelDataTemplate">
|
||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Data.ShowDataModelValues.Value, Source={StaticResource DataContextProxy}}" Value="True">
|
||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Button Background="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
BorderBrush="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Style="{StaticResource DataModelConditionButton}"
|
||||
IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
HorizontalAlignment="Left"
|
||||
Click="PropertyButton_OnClick"
|
||||
x:Name="DataModelButton">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu.Resources>
|
||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||
<Setter Property="ItemsSource" Value="{Binding Children}" />
|
||||
<Setter Property="Command" Value="{Binding Data.SelectPropertyCommand, Source={StaticResource DataContextProxy}}" />
|
||||
<Setter Property="CommandParameter" Value="{Binding}" />
|
||||
<Setter Property="CommandTarget" Value="{Binding}" />
|
||||
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
|
||||
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
|
||||
</Style>
|
||||
<shared:IntToVisibilityConverter x:Key="IntToVisibilityConverter" />
|
||||
</ContextMenu.Resources>
|
||||
<ContextMenu.ItemsSource>
|
||||
<CompositeCollection>
|
||||
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
||||
<Separator Visibility="{Binding Data.ExtraDataModelViewModels.Count, Source={StaticResource DataContextProxy}, Converter={StaticResource IntToVisibilityConverter}, Mode=OneWay}" />
|
||||
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
||||
</CompositeCollection>
|
||||
</ContextMenu.ItemsSource>
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Grid>
|
||||
<Grid x:Name="ValueDisplay">
|
||||
<TextBlock x:Name="ValueDisplayTextBlock"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
||||
<TextBlock FontStyle="Italic"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
||||
<Run Text="« " /><Run Text="{Binding Placeholder, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" /><Run Text=" »" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock x:Name="ValuePlaceholder" FontStyle="Italic">
|
||||
« Invalid »
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
303
src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs
Normal file
303
src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs
Normal file
@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Shared.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DataModelPicker.xaml
|
||||
/// </summary>
|
||||
public partial class DataModelPicker : INotifyPropertyChanged
|
||||
{
|
||||
private static IDataModelUIService _dataModelUIService;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets data model path
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataModelPathProperty = DependencyProperty.Register(
|
||||
nameof(DataModelPath), typeof(DataModelPath), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DataModelPathPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
|
||||
nameof(Placeholder), typeof(string), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata("Click to select")
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowDataModelValuesProperty = DependencyProperty.Register(nameof(ShowDataModelValues), typeof(bool), typeof(DataModelPicker));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register(nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ButtonBrushProperty = DependencyProperty.Register(nameof(ButtonBrush), typeof(Brush), typeof(DataModelPicker));
|
||||
|
||||
/// <summary>
|
||||
/// A list of extra modules to show data models of
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ModulesProperty = DependencyProperty.Register(
|
||||
nameof(Modules), typeof(BindableCollection<Module>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new BindableCollection<Module>(), FrameworkPropertyMetadataOptions.None, ModulesPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// The data model view model to show, if not provided one will be retrieved by the control
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataModelViewModelProperty = DependencyProperty.Register(
|
||||
nameof(DataModelViewModel), typeof(DataModelPropertiesViewModel), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, DataModelViewModelPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// A list of data model view models to show
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ExtraDataModelViewModelsProperty = DependencyProperty.Register(
|
||||
nameof(ExtraDataModelViewModels), typeof(BindableCollection<DataModelPropertiesViewModel>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new BindableCollection<DataModelPropertiesViewModel>(), FrameworkPropertyMetadataOptions.None, ExtraDataModelViewModelsPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// A list of data model view models to show
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FilterTypesProperty = DependencyProperty.Register(
|
||||
nameof(FilterTypes), typeof(BindableCollection<Type>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new BindableCollection<Type>())
|
||||
);
|
||||
|
||||
public DataModelPicker()
|
||||
{
|
||||
SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand);
|
||||
|
||||
InitializeComponent();
|
||||
GetDataModel();
|
||||
UpdateValueDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public DataModelPath? DataModelPath
|
||||
{
|
||||
get => (DataModelPath) GetValue(DataModelPathProperty);
|
||||
set => SetValue(DataModelPathProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string) GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public bool ShowDataModelValues
|
||||
{
|
||||
get => (bool) GetValue(ShowDataModelValuesProperty);
|
||||
set => SetValue(ShowDataModelValuesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public bool ShowFullPath
|
||||
{
|
||||
get => (bool) GetValue(ShowFullPathProperty);
|
||||
set => SetValue(ShowFullPathProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public Brush ButtonBrush
|
||||
{
|
||||
get => (Brush) GetValue(ButtonBrushProperty);
|
||||
set => SetValue(ButtonBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of extra modules to show data models of
|
||||
/// </summary>
|
||||
public BindableCollection<Module>? Modules
|
||||
{
|
||||
get => (BindableCollection<Module>) GetValue(ModulesProperty);
|
||||
set => SetValue(ModulesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data model view model to show
|
||||
/// </summary>
|
||||
public DataModelPropertiesViewModel? DataModelViewModel
|
||||
{
|
||||
get => (DataModelPropertiesViewModel) GetValue(DataModelViewModelProperty);
|
||||
set => SetValue(DataModelViewModelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of data model view models to show
|
||||
/// </summary>
|
||||
public BindableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
|
||||
{
|
||||
get => (BindableCollection<DataModelPropertiesViewModel>) GetValue(ExtraDataModelViewModelsProperty);
|
||||
set => SetValue(ExtraDataModelViewModelsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the types of properties this view model will allow to be selected
|
||||
/// </summary>
|
||||
public BindableCollection<Type>? FilterTypes
|
||||
{
|
||||
get => (BindableCollection<Type>) GetValue(FilterTypesProperty);
|
||||
set => SetValue(FilterTypesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command used by view
|
||||
/// </summary>
|
||||
public DelegateCommand SelectPropertyCommand { get; }
|
||||
|
||||
internal static IDataModelUIService DataModelUIService
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_dataModelUIService != null)
|
||||
throw new AccessViolationException("This is not for you to touch");
|
||||
_dataModelUIService = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new path has been selected
|
||||
/// </summary>
|
||||
public event EventHandler<DataModelSelectedEventArgs>? DataModelPathSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataModelPathSelected" /> event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
|
||||
{
|
||||
DataModelPathSelected?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private void GetDataModel()
|
||||
{
|
||||
ChangeDataModel(_dataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List<Module>(), true));
|
||||
}
|
||||
|
||||
private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
|
||||
{
|
||||
if (DataModelViewModel != null)
|
||||
{
|
||||
DataModelViewModel.Dispose();
|
||||
DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
|
||||
}
|
||||
|
||||
DataModelViewModel = dataModel;
|
||||
if (DataModelViewModel != null)
|
||||
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
||||
}
|
||||
|
||||
private void UpdateValueDisplay()
|
||||
{
|
||||
ValueDisplay.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Visible : Visibility.Collapsed;
|
||||
ValuePlaceholder.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
string? formattedPath = null;
|
||||
if (DataModelPath != null && DataModelPath.IsValid)
|
||||
formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
|
||||
|
||||
DataModelButton.ToolTip = formattedPath;
|
||||
ValueDisplayTextBlock.Text = ShowFullPath
|
||||
? formattedPath
|
||||
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
|
||||
}
|
||||
|
||||
private void DataModelOnUpdateRequested(object? sender, EventArgs e)
|
||||
{
|
||||
DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
||||
if (ExtraDataModelViewModels == null) return;
|
||||
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
|
||||
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
||||
}
|
||||
|
||||
private void ExecuteSelectPropertyCommand(object? context)
|
||||
{
|
||||
if (context is not DataModelVisualizationViewModel selected)
|
||||
return;
|
||||
|
||||
DataModelPath = selected.DataModelPath;
|
||||
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
|
||||
}
|
||||
|
||||
private void PropertyButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// DataContext is not set when using left button, I don't know why but there it is
|
||||
if (sender is Button button && button.ContextMenu != null)
|
||||
{
|
||||
button.ContextMenu.DataContext = button.DataContext;
|
||||
button.ContextMenu.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DataModelPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
if (e.OldValue is DataModelPath oldPath)
|
||||
oldPath.Dispose();
|
||||
|
||||
dataModelPicker.UpdateValueDisplay();
|
||||
}
|
||||
|
||||
private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
dataModelPicker.GetDataModel();
|
||||
}
|
||||
|
||||
private static void DataModelViewModelPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
}
|
||||
|
||||
private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
}
|
||||
}
|
||||
39
src/Artemis.UI.Shared/Converters/IntToVisibilityConverter.cs
Normal file
39
src/Artemis.UI.Shared/Converters/IntToVisibilityConverter.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="int"/> to <see cref="Visibility"/>
|
||||
/// </summary>
|
||||
public class IntToVisibilityConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Parameters direction;
|
||||
if (parameter == null)
|
||||
direction = Parameters.Normal;
|
||||
else
|
||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
||||
|
||||
return direction == Parameters.Normal
|
||||
? value is > 1 ? Visibility.Visible : Visibility.Collapsed
|
||||
: value is > 1 ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private enum Parameters
|
||||
{
|
||||
Normal,
|
||||
Inverted
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.Input.DataModelDynamicView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
xmlns:input="clr-namespace:Artemis.UI.Shared.Input"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance input:DataModelDynamicViewModel}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||
<ResourceDictionary>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<shared:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||
<DataTemplate x:Key="DataModelDataTemplate">
|
||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Data.ShowDataModelValues.Value, Source={StaticResource DataContextProxy}}" Value="True">
|
||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
<Style x:Key="DataModelConditionButtonCornerToggle" BasedOn="{StaticResource DataModelConditionButton}" TargetType="Button">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DisplaySwitchButton}" Value="True">
|
||||
<Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="0 2 2 0" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding DisplaySwitchButton}" Value="False">
|
||||
<Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="2" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignFlatDarkBgButton}"
|
||||
Background="{Binding SwitchButtonBrush}"
|
||||
BorderBrush="{Binding SwitchButtonBrush}"
|
||||
Height="22"
|
||||
Padding="0"
|
||||
Width="22"
|
||||
FontSize="12"
|
||||
materialDesign:ButtonAssist.CornerRadius="2 0 0 2"
|
||||
Margin="3 0 -3 0"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip="Switch to manual input"
|
||||
Command="{s:Action SwitchToStatic}"
|
||||
Visibility="{Binding DisplaySwitchButton, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<materialDesign:PackIcon Kind="SwapHorizontal" />
|
||||
</Button>
|
||||
<Button Background="{Binding ButtonBrush}"
|
||||
BorderBrush="{Binding ButtonBrush}"
|
||||
Style="{StaticResource DataModelConditionButtonCornerToggle}"
|
||||
ToolTip="{Binding DisplayPath}"
|
||||
IsEnabled="{Binding IsEnabled}"
|
||||
HorizontalAlignment="Left"
|
||||
Click="PropertyButton_OnClick">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu IsOpen="{Binding IsDataModelViewModelOpen, Mode=OneWayToSource}">
|
||||
<ContextMenu.Resources>
|
||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||
<Setter Property="ItemsSource" Value="{Binding Children}" />
|
||||
<Setter Property="Command" Value="{Binding Data.SelectPropertyCommand, Source={StaticResource DataContextProxy}}" />
|
||||
<Setter Property="CommandParameter" Value="{Binding}" />
|
||||
<Setter Property="CommandTarget" Value="{Binding}" />
|
||||
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
|
||||
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
|
||||
</Style>
|
||||
</ContextMenu.Resources>
|
||||
<ContextMenu.ItemsSource>
|
||||
<CompositeCollection>
|
||||
<MenuItem Header="No modules available" IsEnabled="False" Visibility="{Binding Data.HasNoModules, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="EmojiSad" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
||||
<Separator Visibility="{Binding Data.HasExtraDataModels, Source={StaticResource DataContextProxy}, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"/>
|
||||
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
||||
</CompositeCollection>
|
||||
</ContextMenu.ItemsSource>
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Grid>
|
||||
<Grid Visibility="{Binding IsValid,Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<TextBlock Text="{Binding DisplayValue}"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock FontStyle="Italic"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
|
||||
<Run Text="« " /><Run Text="{Binding Placeholder}" /><Run Text=" »" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock FontStyle="Italic"
|
||||
Visibility="{Binding IsValid, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
« Invalid »
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user