1
0
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:
Robert 2021-08-18 23:47:14 +02:00
parent 32d4ec2812
commit 5675d1895b
157 changed files with 784 additions and 8802 deletions

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class EnumContainsConditionOperator : ConditionOperator<Enum, Enum>
{
public override string Description => "Contains";
public override string Icon => "Contain";
public override bool Evaluate(Enum a, Enum b)
{
return a != null && b != null && a.HasFlag(b);
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class EnumNotContainsConditionOperator : ConditionOperator<Enum, Enum>
{
public override string Description => "Does not contain";
public override string Icon => "FormatStrikethrough";
public override bool Evaluate(Enum a, Enum b)
{
return a != null && (b == null || !a.HasFlag(b));
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class EqualsConditionOperator : ConditionOperator<object, object>
{
public override string Description => "Equals";
public override string Icon => "Equal";
public override bool Evaluate(object a, object b)
{
return Equals(a, b);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class GreaterThanConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Is greater than";
public override string Icon => "GreaterThan";
public override bool Evaluate(double a, double b)
{
return a > b;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class GreaterThanOrEqualConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Is greater than or equal to";
public override string Icon => "GreaterThanOrEqual";
public override bool Evaluate(double a, double b)
{
return a >= b;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class LessThanConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Is less than";
public override string Icon => "LessThan";
public override bool Evaluate(double a, double b)
{
return a < b;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class LessThanOrEqualConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Is less than or equal to";
public override string Icon => "LessThanOrEqual";
public override bool Evaluate(double a, double b)
{
return a <= b;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class NotEqualConditionOperator : ConditionOperator<object, object>
{
public override string Description => "Does not equal";
public override string Icon => "NotEqualVariant";
public override bool Evaluate(object a, object b)
{
return !Equals(a, b);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class NotNullConditionOperator : ConditionOperator<object>
{
public override string Description => "Is not null";
public override string Icon => "CheckboxMarkedCircleOutline";
public override bool Evaluate(object a)
{
return a != null;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class NullConditionOperator : ConditionOperator<object>
{
public override string Description => "Is null";
public override string Icon => "Null";
public override bool Evaluate(object a)
{
return a == null;
}
}
}

View File

@ -1,16 +0,0 @@
using System;
namespace Artemis.Core
{
internal class NumberEqualsConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Equals";
public override string Icon => "Equal";
public override bool Evaluate(double a, double b)
{
// Numbers can be tricky, an epsilon like this is close enough
return Math.Abs(a - b) < 0.000001;
}
}
}

View File

@ -1,16 +0,0 @@
using System;
namespace Artemis.Core
{
internal class NumberNotEqualConditionOperator : ConditionOperator<double, double>
{
public override string Description => "Does not equal";
public override string Icon => "NotEqualVariant";
public override bool Evaluate(double a, double b)
{
// Numbers can be tricky, an epsilon like this is close enough
return Math.Abs(a - b) > 0.000001;
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringContainsConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Contains";
public override string Icon => "Contain";
public override bool Evaluate(string a, string b)
{
return a != null && b != null && a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringEndsWithConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Ends with";
public override string Icon => "ContainEnd";
public override bool Evaluate(string a, string b)
{
return a != null && b != null && a.EndsWith(b, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringEqualsConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Equals";
public override string Icon => "Equal";
public override bool Evaluate(string a, string b)
{
return string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@ -1,20 +0,0 @@
using System.Text.RegularExpressions;
namespace Artemis.Core {
internal class StringMatchesRegexConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Matches Regex";
public override string Icon => "Regex";
public override bool Evaluate(string text, string regex)
{
// Ensures full match
if (!regex.StartsWith("^"))
regex = "^" + regex;
if (!regex.EndsWith("$"))
regex += "$";
return Regex.IsMatch(text, regex);
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringNotContainsConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Does not contain";
public override string Icon => "FormatStrikethrough";
public override bool Evaluate(string a, string b)
{
return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase));
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringNotEqualConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Does not equal";
public override string Icon => "NotEqualVariant";
public override bool Evaluate(string a, string b)
{
return !string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class StringNotNullConditionOperator : ConditionOperator<string>
{
public override string Description => "Is not null";
public override string Icon => "CheckboxMarkedCircleOutline";
public override bool Evaluate(string a)
{
return !string.IsNullOrWhiteSpace(a);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class StringNullConditionOperator : ConditionOperator<string>
{
public override string Description => "Is null";
public override string Icon => "Null";
public override bool Evaluate(string a)
{
return string.IsNullOrWhiteSpace(a);
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
internal class StringStartsWithConditionOperator : ConditionOperator<string, string>
{
public override string Description => "Starts with";
public override string Icon => "ContainStart";
public override bool Evaluate(string a, string b)
{
return a != null && b != null && a.StartsWith(b, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@ -1,18 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorBrightenModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Brighten by";
public override string Icon => "CarLightHigh";
public override string Description => "Brightens the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
currentValue.ToHsl(out float h, out float s, out float l);
l *= (parameterValue + 100f) / 100f;
return SKColor.FromHsl(h, s, l);
}
}
}

View File

@ -1,18 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorDarkenModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Darken by";
public override string Icon => "CarLightDimmed";
public override string Description => "Darkens the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
currentValue.ToHsl(out float h, out float s, out float l);
l *= (parameterValue * -1 + 100f) / 100f;
return SKColor.FromHsl(h, s, l);
}
}
}

View File

@ -1,21 +0,0 @@
using System;
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorDesaturateModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Desaturate";
public override string Icon => "ImageMinus";
public override string Description => "Desaturates the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
currentValue.ToHsl(out float h, out float s, out float l);
s -= parameterValue;
s = Math.Clamp(s, 0, 100);
return SKColor.FromHsl(h, s, l, currentValue.Alpha);
}
}
}

View File

@ -1,18 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorInvertModifierType : DataBindingModifierType<SKColor>
{
public override string Name => "Invert color";
public override string Icon => "InvertColors";
public override string Description => "Inverts the color by rotating its hue by a 180°";
public override SKColor Apply(SKColor currentValue)
{
currentValue.ToHsl(out float h, out float s, out float l);
h += 180;
return SKColor.FromHsl(h % 360, s, l);
}
}
}

View File

@ -1,18 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorRotateHueModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Rotate Hue by";
public override string Icon => "RotateRight";
public override string Description => "Rotates the hue of the color by the amount in degrees";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
currentValue.ToHsl(out float h, out float s, out float l);
h += parameterValue;
return SKColor.FromHsl(h % 360, s, l);
}
}
}

View File

@ -1,21 +0,0 @@
using System;
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorSaturateModifierType : DataBindingModifierType<SKColor, float>
{
public override string Name => "Saturate";
public override string Icon => "ImagePlus";
public override string Description => "Saturates the color by the amount in percent";
public override SKColor Apply(SKColor currentValue, float parameterValue)
{
currentValue.ToHsv(out float h, out float s, out float v);
s += parameterValue;
s = Math.Clamp(s, 0, 100);
return SKColor.FromHsv(h, s, v, currentValue.Alpha);
}
}
}

View File

@ -1,16 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
internal class SKColorSumModifierType : DataBindingModifierType<SKColor, SKColor>
{
public override string Name => "Combine with";
public override string Icon => "FormatColorFill";
public override string Description => "Adds the two colors together";
public override SKColor Apply(SKColor currentValue, SKColor parameterValue)
{
return currentValue.Sum(parameterValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class AbsoluteModifierType : DataBindingModifierType<double>
{
public override string Name => "Absolute";
public override string Icon => "NumericPositive1";
public override string Category => "Advanced";
public override string Description => "Converts the input value to an absolute value";
public override double Apply(double currentValue)
{
return Math.Abs(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
namespace Artemis.Core
{
internal class DivideModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Divide by";
public override string Icon => "Divide";
public override double Apply(double currentValue, double parameterValue)
{
// Ye ye none of that
if (parameterValue == 0)
return 0;
return currentValue / parameterValue;
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class MaxModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Max";
public override string Icon => "ChevronUpBoxOutline";
public override string Category => "Advanced";
public override string Description => "Keeps only the largest of input value and parameter";
public override double Apply(double currentValue, double parameterValue)
{
return Math.Max(currentValue, parameterValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class MinModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Min";
public override string Icon => "ChevronDownBoxOutline";
public override string Category => "Advanced";
public override string Description => "Keeps only the smallest of input value and parameter";
public override double Apply(double currentValue, double parameterValue)
{
return Math.Min(currentValue, parameterValue);
}
}
}

View File

@ -1,15 +0,0 @@
namespace Artemis.Core
{
internal class ModuloModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Modulo";
public override string Icon => "Stairs";
public override string Category => "Advanced";
public override string Description => "Calculates the remained of the division between the input value and the parameter";
public override double Apply(double currentValue, double parameterValue)
{
return currentValue % parameterValue;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class MultiplicationModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Multiply by";
public override string Icon => "Close";
public override double Apply(double currentValue, double parameterValue)
{
return currentValue * parameterValue;
}
}
}

View File

@ -1,18 +0,0 @@
namespace Artemis.Core
{
internal class PercentageOfModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Percentage of";
public override string Icon => "Percent";
public override string Description => "Calculates how much percent the parameter value is of the current value";
public override double Apply(double currentValue, double parameterValue)
{
// Ye ye none of that
if (parameterValue == 0d)
return 100d;
return 100d / parameterValue * currentValue;
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class PowerModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Power";
public override string Icon => "Exponent";
public override string Category => "Advanced";
public override string Description => "Raises the input value to the power of the parameter value";
public override double Apply(double currentValue, double parameterValue)
{
return Math.Pow(currentValue, parameterValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class CeilingModifierType : DataBindingModifierType<double>
{
public override string Name => "Round up";
public override string Icon => "ArrowUp";
public override string Category => "Rounding";
public override string Description => "Ceils the input, rounding it up to the nearest whole number";
public override double Apply(double currentValue)
{
return Math.Ceiling(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class FloorModifierType : DataBindingModifierType<double>
{
public override string Name => "Round down";
public override string Icon => "ArrowDown";
public override string Category => "Rounding";
public override string Description => "Floors the input, rounding it down to the nearest whole number";
public override double Apply(double currentValue)
{
return Math.Floor(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class RoundModifierType : DataBindingModifierType<double>
{
public override string Name => "Round";
public override string Icon => "ArrowCollapse";
public override string Category => "Rounding";
public override string Description => "Rounds the input to the nearest whole number";
public override double Apply(double currentValue)
{
return Math.Round(currentValue, MidpointRounding.AwayFromZero);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class SquareRootModifierType : DataBindingModifierType<double>
{
public override string Name => "Square root";
public override string Icon => "SquareRoot";
public override string Category => "Advanced";
public override string Description => "Calculates square root of the input value";
public override double Apply(double currentValue)
{
return Math.Sqrt(currentValue);
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class SubtractModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Subtract";
public override string Icon => "Minus";
public override double Apply(double currentValue, double parameterValue)
{
return currentValue - parameterValue;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Artemis.Core
{
internal class SumModifierType : DataBindingModifierType<double, double>
{
public override string Name => "Sum";
public override string Icon => "Plus";
public override double Apply(double currentValue, double parameterValue)
{
return currentValue + parameterValue;
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class CosecantModifierType : DataBindingModifierType<double>
{
public override string Name => "Cosecant";
public override string? Icon => null;
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the cosecant";
public override double Apply(double currentValue)
{
return 1d / Math.Sin(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class CosineModifierType : DataBindingModifierType<double>
{
public override string Name => "Cosine";
public override string Icon => "MathCos";
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the cosine";
public override double Apply(double currentValue)
{
return Math.Cos(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class CotangentModifierType : DataBindingModifierType<double>
{
public override string Name => "Cotangent";
public override string? Icon => null;
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the cotangent";
public override double Apply(double currentValue)
{
return 1d / Math.Tan(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class SecantModifierType : DataBindingModifierType<double>
{
public override string Name => "Secant";
public override string? Icon => null;
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the secant";
public override double Apply(double currentValue)
{
return 1d / Math.Cos(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class SineModifierType : DataBindingModifierType<double>
{
public override string Name => "Sine";
public override string Icon => "MathSin";
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the sine";
public override double Apply(double currentValue)
{
return Math.Sin(currentValue);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace Artemis.Core
{
internal class TangentModifierType : DataBindingModifierType<double>
{
public override string Name => "Tangent";
public override string Icon => "MathTan";
public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the tangent";
public override double Apply(double currentValue)
{
return Math.Tan(currentValue);
}
}
}

View File

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

View File

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

View File

@ -1,82 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition operator that performs a boolean operation
/// <para>
/// To implement your own condition operator, inherit <see cref="ConditionOperator{TLeftSide, TRightSide}" /> or
/// <see cref="ConditionOperator{TLeftSide}" />
/// </para>
/// </summary>
public abstract class BaseConditionOperator
{
/// <summary>
/// Gets or sets the description of this logical operator
/// </summary>
public abstract string Description { get; }
/// <summary>
/// Gets or sets the icon of this logical operator
/// </summary>
public abstract string Icon { get; }
/// <summary>
/// Gets the plugin this condition operator belongs to
/// <para>Note: Not set until after registering</para>
/// </summary>
public Plugin? Plugin { get; internal set; }
/// <summary>
/// Gets the left side type of this condition operator
/// </summary>
public abstract Type LeftSideType { get; }
/// <summary>
/// Gets the right side type of this condition operator. May be null if the operator does not support a right side
/// </summary>
public abstract Type? RightSideType { get; }
/// <summary>
/// Returns whether the given type is supported by the operator
/// </summary>
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
/// <param name="side">Which side of the operator to check, left or right</param>
public bool SupportsType(Type type, ConditionParameterSide side)
{
if (type == null)
return true;
if (side == ConditionParameterSide.Left)
return LeftSideType.IsCastableFrom(type);
return RightSideType != null && RightSideType.IsCastableFrom(type);
}
/// <summary>
/// Evaluates the condition with the input types being provided as objects
/// <para>
/// This leaves the caller responsible for the types matching <see cref="LeftSideType" /> and
/// <see cref="RightSideType" />
/// </para>
/// </summary>
/// <param name="leftSideValue">The left side value, type should match <see cref="LeftSideType" /></param>
/// <param name="rightSideValue">The right side value, type should match <see cref="RightSideType" /></param>
/// <returns>The result of the boolean condition's evaluation</returns>
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
}
/// <summary>
/// Represents a side of a condition parameter
/// </summary>
public enum ConditionParameterSide
{
/// <summary>
/// The left side of a condition parameter
/// </summary>
Left,
/// <summary>
/// The right side of a condition parameter
/// </summary>
Right
}
}

View File

@ -1,96 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition operator that performs a boolean operation using a left- and right-side
/// </summary>
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
{
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <inheritdoc />
public override Type RightSideType => typeof(TRightSide);
/// <summary>
/// Evaluates the operator on a and b
/// </summary>
/// <param name="a">The parameter on the left side of the expression</param>
/// <param name="b">The parameter on the right side of the expression</param>
public abstract bool Evaluate(TLeftSide a, TRightSide b);
/// <inheritdoc />
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
{
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
{
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
else
leftSide = (TLeftSide) leftSideValue;
}
else
{
leftSide = default;
}
TRightSide rightSide;
if (rightSideValue != null)
{
if (rightSideValue.GetType() != typeof(TRightSide) && leftSideValue is IConvertible)
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
else
rightSide = (TRightSide) rightSideValue;
}
else
{
rightSide = default;
}
return Evaluate(leftSide!, rightSide!);
}
}
/// <summary>
/// Represents a condition operator that performs a boolean operation using only a left side
/// </summary>
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
{
/// <inheritdoc />
public override Type LeftSideType => typeof(TLeftSide);
/// <summary>
/// Always <c>null</c>, not applicable to this type of condition operator
/// </summary>
public override Type? RightSideType => null;
/// <summary>
/// Evaluates the operator on a and b
/// </summary>
/// <param name="a">The parameter on the left side of the expression</param>
public abstract bool Evaluate(TLeftSide a);
/// <inheritdoc />
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
{
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
{
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
else
leftSide = (TLeftSide) leftSideValue;
}
else
{
leftSide = default;
}
return Evaluate(leftSide!);
}
}
}

View File

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

View File

@ -1,411 +0,0 @@
using System;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Newtonsoft.Json;
namespace Artemis.Core
{
/// <summary>
/// A predicate in a data model condition using either two data model values or one data model value and a
/// static value
/// </summary>
public abstract class DataModelConditionPredicate : DataModelConditionPart
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
/// <param name="entity">A new empty entity</param>
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
{
Parent = parent;
Entity = entity;
PredicateType = predicateType;
}
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
{
Parent = parent;
Entity = entity;
PredicateType = (ProfileRightSideType) entity.PredicateType;
}
/// <summary>
/// Gets or sets the predicate type
/// </summary>
public ProfileRightSideType PredicateType { get; set; }
/// <summary>
/// Gets the operator
/// </summary>
public BaseConditionOperator? Operator { get; protected set; }
/// <summary>
/// Gets the path of the left property
/// </summary>
public DataModelPath? LeftPath { get; protected set; }
/// <summary>
/// Gets the path of the right property
/// </summary>
public DataModelPath? RightPath { get; protected set; }
/// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ProfileRightSideType.Static" />
/// </summary>
public object? RightStaticValue { get; protected set; }
internal DataModelConditionPredicateEntity Entity { get; set; }
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}";
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
}
#endregion
#region Initialization
internal void Initialize()
{
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
InitializeLeftPath();
// Operator
if (Entity.OperatorPluginGuid != null)
{
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null)
UpdateOperator(conditionOperator);
}
// Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic)
InitializeRightPath();
// Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
{
try
{
// If the left path is not valid we cannot reliably set up the right side because the type is unknown
// Because of that wait for it to validate first
if (LeftPath != null && !LeftPath.IsValid)
{
LeftPath.PathValidated += InitializeRightSideStatic;
return;
}
if (LeftPath == null)
return;
// Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftPath.GetPropertyType()!;
object? rightSideValue;
try
{
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
catch (JsonReaderException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
}
}
}
private void InitializeRightSideStatic(object? sender, EventArgs args)
{
if (LeftPath == null)
return;
LeftPath.PathValidated -= InitializeRightSideStatic;
// Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftPath.GetPropertyType()!;
object? rightSideValue;
try
{
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
rightSideValue = Activator.CreateInstance(leftSideType);
}
UpdateRightSideStatic(rightSideValue);
}
/// <summary>
/// Initializes the left path of this condition predicate
/// </summary>
protected abstract void InitializeLeftPath();
/// <summary>
/// Initializes the right path of this condition predicate
/// </summary>
protected abstract void InitializeRightPath();
#endregion
#region Modification
/// <summary>
/// Updates the left side of the predicate
/// </summary>
/// <param name="path">The path pointing to the left side value inside the data model</param>
public virtual void UpdateLeftSide(DataModelPath? path)
{
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
LeftPath?.Dispose();
LeftPath = path != null ? new DataModelPath(path) : null;
ValidateOperator();
ValidateRightSide();
}
/// <summary>
/// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression
/// </summary>
/// <param name="path">The path pointing to the right side value inside the data model</param>
public void UpdateRightSideDynamic(DataModelPath? path)
{
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
RightPath?.Dispose();
RightPath = path != null ? new DataModelPath(path) : null;
PredicateType = ProfileRightSideType.Dynamic;
}
/// <summary>
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
/// </summary>
/// <param name="staticValue">The right side value to use</param>
public void UpdateRightSideStatic(object? staticValue)
{
PredicateType = ProfileRightSideType.Static;
RightPath?.Dispose();
RightPath = null;
// If the operator is null simply apply the value, any validation will wait
if (Operator == null)
{
RightStaticValue = staticValue;
return;
}
// If the operator does not support a right side, always set it to null
if (Operator.RightSideType == null)
{
RightStaticValue = null;
return;
}
// If not null ensure the types match and if not, convert it
Type? preferredType = GetPreferredRightSideType();
if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null)
RightStaticValue = staticValue;
else if (staticValue != null)
RightStaticValue = Convert.ChangeType(staticValue, preferredType);
// If null create a default instance for value types or simply make it null for reference types
else if (preferredType.IsValueType)
RightStaticValue = Activator.CreateInstance(preferredType);
else
RightStaticValue = null;
}
/// <summary>
/// Updates the operator of the predicate and re-compiles the expression
/// </summary>
/// <param name="conditionOperator"></param>
public void UpdateOperator(BaseConditionOperator? conditionOperator)
{
if (conditionOperator == null)
{
Operator = null;
return;
}
// No need to check for compatibility without a left side, when left site does get set it will be checked again
if (LeftPath == null || !LeftPath.IsValid)
{
Operator = conditionOperator;
return;
}
Type leftType = LeftPath.GetPropertyType()!;
// Left side can't go empty so enforce a match
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}");
Operator = conditionOperator;
ValidateRightSide();
}
/// <summary>
/// Determines the best type to use for the right side op this predicate
/// </summary>
public abstract Type? GetPreferredRightSideType();
private void ValidateOperator()
{
if (LeftPath == null || !LeftPath.IsValid || Operator == null)
return;
Type leftType = LeftPath.GetPropertyType()!;
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
Operator = null;
}
private void ValidateRightSide()
{
if (Operator == null)
return;
if (PredicateType == ProfileRightSideType.Dynamic)
{
if (RightPath == null || !RightPath.IsValid)
return;
Type rightSideType = RightPath.GetPropertyType()!;
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
UpdateRightSideDynamic(null);
}
else
{
if (RightStaticValue == null)
return;
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
UpdateRightSideStatic(null);
}
}
#endregion
#region Evaluation
/// <inheritdoc />
public override bool Evaluate()
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// If the operator does not support a right side, immediately evaluate with null
if (Operator.RightSideType == null)
return Operator.InternalEvaluate(LeftPath.GetValue(), null);
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = LeftPath.GetValue();
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
}
/// <inheritdoc />
internal override bool EvaluateObject(object? target)
{
return false;
}
#endregion
#region Storage
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
internal override void Save()
{
// Don't save an invalid state
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
return;
Entity.PredicateType = (int) PredicateType;
LeftPath?.Save();
Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = CoreJson.SerializeObject(RightStaticValue);
if (Operator?.Plugin != null)
{
Entity.OperatorPluginGuid = Operator.Plugin.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
}
#endregion
#region Event handlers
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
{
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.Plugin!.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator);
}
private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
{
if (e.Registration.ConditionOperator != Operator)
return;
Operator = null;
}
#endregion
}
}

View File

@ -1,257 +0,0 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A condition that evaluates to true when an event is triggered
/// </summary>
public class DataModelConditionEvent : DataModelConditionPart
{
private bool _disposed;
private bool _reinitializing;
private IDataModelEvent? _valueChangedEvent;
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
/// </summary>
/// <param name="parent"></param>
public DataModelConditionEvent(DataModelConditionPart parent)
{
Parent = parent;
Entity = new DataModelConditionEventEntity();
Initialize();
}
internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
{
Parent = parent;
Entity = entity;
Initialize();
}
/// <summary>
/// Gets the path of the event property
/// </summary>
public DataModelPath? EventPath { get; private set; }
/// <summary>
/// Gets the last time the event this condition is applied to was triggered
/// </summary>
public DateTime LastTrigger { get; private set; }
/// <summary>
/// Gets or sets the type of argument the event provides
/// </summary>
public Type? EventArgumentType { get; private set; }
internal DataModelConditionEventEntity Entity { get; set; }
/// <inheritdoc />
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionEvent");
IDataModelEvent? dataModelEvent = GetDataModelEvent();
if (dataModelEvent == null)
return false;
dataModelEvent.Update();
// Only evaluate to true once every time the event has been triggered since the last evaluation
if (dataModelEvent.LastTrigger <= LastTrigger)
return false;
LastTrigger = DateTime.Now;
// If there is a child (root group), it must evaluate to true whenever the event triggered
if (Children.Any())
return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped);
// If there are no children, we always evaluate to true whenever the event triggered
return true;
}
/// <summary>
/// Updates the event the condition is triggered by
/// </summary>
public void UpdateEvent(DataModelPath? path)
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionEvent");
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update event to an invalid path");
EventPath?.Dispose();
EventPath = path != null ? new DataModelPath(path) : null;
SubscribeToEventPath();
CreateValueChangedEventIfNeeded();
// Remove the old root group that was tied to the old data model
ClearChildren();
if (EventPath != null)
{
EventArgumentType = GetEventArgumentType();
// Create a new root group
AddChild(new DataModelConditionGroup(this));
}
else
{
EventArgumentType = null;
}
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
}
/// <summary>
/// Returns the <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by
/// </summary>
/// <returns>The <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by</returns>
public IDataModelEvent? GetDataModelEvent()
{
if (_valueChangedEvent != null)
return _valueChangedEvent;
return EventPath?.GetValue() as IDataModelEvent;
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
EventPath?.Dispose();
foreach (DataModelConditionPart child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object? target)
{
return false;
}
internal override void Save()
{
// Don't save an invalid state
if (EventPath != null && !EventPath.IsValid)
return;
// Target list
EventPath?.Save();
Entity.EventPath = EventPath?.Entity;
// Children
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (DataModelConditionPart child in Children)
child.Save();
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
internal void Initialize()
{
ClearChildren();
if (Entity.EventPath == null)
return;
DataModelPath eventPath = new(null, Entity.EventPath);
EventPath = eventPath;
SubscribeToEventPath();
CreateValueChangedEventIfNeeded();
EventArgumentType = GetEventArgumentType();
// There should only be one child and it should be a group
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
{
AddChild(new DataModelConditionGroup(this, rootGroup));
}
else
{
Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this));
}
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
}
private Type? GetEventArgumentType()
{
if (EventPath == null || !EventPath.IsValid)
return null;
if (_valueChangedEvent != null)
return _valueChangedEvent.ArgumentsType;
// Cannot rely on EventPath.GetValue() because part of the path might be null
Type eventType = EventPath.GetPropertyType()!;
return eventType.IsGenericType ? eventType.GetGenericArguments()[0] : typeof(DataModelEventArgs);
}
private void SubscribeToEventPath()
{
if (EventPath == null) return;
EventPath.PathValidated += EventPathOnPathValidated;
EventPath.PathInvalidated += EventPathOnPathInvalidated;
}
private void CreateValueChangedEventIfNeeded()
{
Type? propertyType = EventPath?.GetPropertyType();
if (propertyType == null)
return;
if (!typeof(IDataModelEvent).IsAssignableFrom(propertyType))
{
IDataModelEvent? instance = (IDataModelEvent?) Activator.CreateInstance(typeof(DataModelValueChangedEvent<>).MakeGenericType(propertyType), EventPath);
_valueChangedEvent = instance ?? throw new ArtemisCoreException("Failed to create a DataModelValueChangedEvent for a property changed data model event");
}
else
{
_valueChangedEvent = null;
}
}
#region Event handlers
private void EventPathOnPathValidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
EventPath?.Dispose();
Initialize();
_reinitializing = false;
}
private void EventPathOnPathInvalidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
EventPath?.Dispose();
Initialize();
_reinitializing = false;
}
#endregion
}
}

View File

@ -1,163 +0,0 @@
using System;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A predicate like evaluated inside a <see cref="DataModelConditionEvent" />
/// </summary>
public class DataModelConditionEventPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEventPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionEventPredicateEntity())
{
DataModelConditionEvent = null!;
ApplyParentEvent();
Initialize();
}
internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity)
: base(parent, entity)
{
DataModelConditionEvent = null!;
ApplyParentEvent();
Initialize();
}
/// <summary>
/// Gets the data model condition event this predicate belongs to
/// </summary>
public DataModelConditionEvent DataModelConditionEvent { get; private set; }
private void ApplyParentEvent()
{
DataModelConditionPart? current = Parent;
while (current != null)
{
if (current is DataModelConditionEvent parentEvent)
{
DataModelConditionEvent = parentEvent;
return;
}
current = current.Parent;
}
if (DataModelConditionEvent == null)
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event");
}
private object? GetEventPathValue(DataModelPath path, object? target)
{
lock (path)
{
if (!(path.Target is EventPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target");
wrapper.UntypedArguments = target;
return path.GetValue();
}
}
#region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = DataModelConditionEvent.EventArgumentType != null
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
: null;
}
/// <inheritdoc />
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{
// Right side dynamic using event arguments
if (Entity.RightPath.WrapperType == PathWrapperType.Event)
{
RightPath = DataModelConditionEvent.EventArgumentType != null
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath)
: null;
}
// Right side dynamic
else
RightPath = new DataModelPath(null, Entity.RightPath);
}
}
#endregion
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Evaluation
/// <summary>
/// Not supported for event predicates, always returns <c>false</c>
/// </summary>
public override bool Evaluate()
{
return false;
}
internal override bool EvaluateObject(object? target)
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// If the operator does not support a right side, immediately evaluate with null
if (Operator.RightSideType == null)
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null);
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = GetEventPathValue(LeftPath, target);
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
if (PredicateType == ProfileRightSideType.Dynamic)
{
// If the path targets a property inside the event, evaluate on the event path value instead of the right path value
if (RightPath.Target is EventPredicateWrapperDataModel)
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target));
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue());
}
return false;
}
#endregion
}
}

View File

@ -1,65 +0,0 @@
using System;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A predicate in a data model condition using either two data model values or one data model value and a
/// static value
/// </summary>
public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionGeneralPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
{
Initialize();
}
internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
: base(parent, entity)
{
Initialize();
}
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = new DataModelPath(null, Entity.LeftPath);
}
/// <inheritdoc />
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
RightPath = new DataModelPath(null, Entity.RightPath);
}
#endregion
}
}

View File

@ -1,190 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A group containing zero to many <see cref="DataModelConditionPart" />s which it evaluates using a boolean specific
/// operator
/// </summary>
public class DataModelConditionGroup : DataModelConditionPart
{
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
/// </summary>
/// <param name="parent"></param>
public DataModelConditionGroup(DataModelConditionPart? parent)
{
Parent = parent;
Entity = new DataModelConditionGroupEntity();
ChildAdded += OnChildrenChanged;
ChildRemoved += OnChildrenChanged;
}
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="entity"></param>
public DataModelConditionGroup(DataModelConditionPart? parent, DataModelConditionGroupEntity entity)
{
Parent = parent;
Entity = entity;
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
foreach (DataModelConditionPartEntity childEntity in Entity.Children)
{
if (childEntity is DataModelConditionGroupEntity groupEntity)
AddChild(new DataModelConditionGroup(this, groupEntity));
else if (childEntity is DataModelConditionListEntity listEntity)
AddChild(new DataModelConditionList(this, listEntity));
else if (childEntity is DataModelConditionEventEntity eventEntity)
AddChild(new DataModelConditionEvent(this, eventEntity));
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
}
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
ChildAdded += OnChildrenChanged;
ChildRemoved += OnChildrenChanged;
}
/// <summary>
/// Gets or sets the boolean operator of this group
/// </summary>
public BooleanOperator BooleanOperator { get; set; }
/// <summary>
/// Gets whether this group contains any events
/// </summary>
public bool ContainsEvents { get; private set; }
internal DataModelConditionGroupEntity Entity { get; set; }
/// <inheritdoc />
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionGroup");
// Empty groups are always true
if (Children.Count == 0)
return true;
// Groups with only one child ignore the boolean operator
if (Children.Count == 1)
return Children[0].Evaluate();
if (ContainsEvents)
{
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
}
return EvaluateWithOperator(Children);
}
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
{
return BooleanOperator switch
{
BooleanOperator.And => targets.All(c => c.Evaluate()),
BooleanOperator.Or => targets.Any(c => c.Evaluate()),
BooleanOperator.AndNot => targets.All(c => !c.Evaluate()),
BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()),
_ => throw new ArgumentOutOfRangeException()
};
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
foreach (DataModelConditionPart child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
/// <inheritdoc />
internal override bool EvaluateObject(object? target)
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionGroup");
// Empty groups are always true
if (Children.Count == 0)
return true;
// Groups with only one child ignore the boolean operator
if (Children.Count == 1)
return Children[0].EvaluateObject(target);
return BooleanOperator switch
{
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void Save()
{
Entity.BooleanOperator = (int) BooleanOperator;
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (DataModelConditionPart child in Children)
child.Save();
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
private void OnChildrenChanged(object? sender, EventArgs e)
{
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
}
}
/// <summary>
/// Represents a boolean operator
/// </summary>
public enum BooleanOperator
{
/// <summary>
/// All the conditions in the group should evaluate to true
/// </summary>
And,
/// <summary>
/// Any of the conditions in the group should evaluate to true
/// </summary>
Or,
/// <summary>
/// All the conditions in the group should evaluate to false
/// </summary>
AndNot,
/// <summary>
/// Any of the conditions in the group should evaluate to false
/// </summary>
OrNot
}
}

View File

@ -1,276 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A condition that evaluates one or more predicates inside a list
/// </summary>
public class DataModelConditionList : DataModelConditionPart
{
private bool _disposed;
private bool _reinitializing;
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionList" /> class
/// </summary>
/// <param name="parent"></param>
public DataModelConditionList(DataModelConditionPart parent)
{
Parent = parent;
Entity = new DataModelConditionListEntity();
Initialize();
}
internal DataModelConditionList(DataModelConditionPart parent, DataModelConditionListEntity entity)
{
Parent = parent;
Entity = entity;
ListOperator = (ListOperator) entity.ListOperator;
Initialize();
}
/// <summary>
/// Gets or sets the list operator
/// </summary>
public ListOperator ListOperator { get; set; }
/// <summary>
/// Gets the path of the list property
/// </summary>
public DataModelPath? ListPath { get; private set; }
/// <summary>
/// Gets the type of the content of the list this predicate is evaluated on
/// </summary>
public Type? ListType { get; set; }
/// <summary>
/// Gets whether the list contains primitives
/// </summary>
public bool IsPrimitiveList { get; set; }
internal DataModelConditionListEntity Entity { get; set; }
/// <inheritdoc />
public override bool Evaluate()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionList");
if (ListPath == null || !ListPath.IsValid)
return false;
return EvaluateObject(ListPath.GetValue());
}
/// <summary>
/// Updates the list the predicate is evaluated on
/// </summary>
/// <param name="path">The path pointing to the list inside the list</param>
public void UpdateList(DataModelPath? path)
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionList");
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update list to an invalid path");
ListPath?.Dispose();
ListPath = path != null ? new DataModelPath(path) : null;
SubscribeToListPath();
// Remove the old root group that was tied to the old data model
while (Children.Any())
RemoveChild(Children[0]);
if (ListPath != null)
{
Type listType = ListPath.GetPropertyType()!;
ListType = listType.GetGenericEnumerableType();
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
// Create a new root group
AddChild(new DataModelConditionGroup(this));
}
else
{
ListType = null;
}
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_disposed = true;
ListPath?.Dispose();
foreach (DataModelConditionPart child in Children)
child.Dispose();
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object? target)
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionList");
if (!Children.Any())
return false;
if (!(target is IEnumerable enumerable))
return false;
IEnumerable<object> objectList = enumerable.Cast<object>();
return ListOperator switch
{
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
ListOperator.Count => false,
_ => throw new ArgumentOutOfRangeException()
};
}
internal override void Save()
{
// Don't save an invalid state
if (ListPath != null && !ListPath.IsValid)
return;
// Target list
ListPath?.Save();
Entity.ListPath = ListPath?.Entity;
// Operator
Entity.ListOperator = (int) ListOperator;
// Children
Entity.Children.Clear();
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
foreach (DataModelConditionPart child in Children)
child.Save();
}
internal override DataModelConditionPartEntity GetEntity()
{
return Entity;
}
internal void Initialize()
{
while (Children.Any())
RemoveChild(Children[0]);
if (Entity.ListPath == null)
return;
// Ensure the list path is valid and points to a list
DataModelPath listPath = new(null, Entity.ListPath);
Type listType = listPath.GetPropertyType()!;
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
if (listPath.IsValid && !PointsToList(listPath))
return;
ListPath = listPath;
SubscribeToListPath();
if (ListPath.IsValid)
{
ListType = listType.GetGenericEnumerableType();
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
}
else
{
ListType = null;
IsPrimitiveList = false;
}
// There should only be one child and it should be a group
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
{
AddChild(new DataModelConditionGroup(this, rootGroup));
}
else
{
Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this));
}
}
private bool PointsToList(DataModelPath dataModelPath)
{
Type? type = dataModelPath.GetPropertyType();
return type?.IsGenericEnumerable() ?? false;
}
private void SubscribeToListPath()
{
if (ListPath == null) return;
ListPath.PathValidated += ListPathOnPathValidated;
ListPath.PathInvalidated += ListPathOnPathInvalidated;
}
#region Event handlers
private void ListPathOnPathValidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
ListPath?.Dispose();
Initialize();
_reinitializing = false;
}
private void ListPathOnPathInvalidated(object? sender, EventArgs e)
{
if (_reinitializing)
return;
_reinitializing = true;
ListPath?.Dispose();
Initialize();
_reinitializing = false;
}
#endregion
}
/// <summary>
/// Represents a list operator
/// </summary>
public enum ListOperator
{
/// <summary>
/// Any of the list items should evaluate to true
/// </summary>
Any,
/// <summary>
/// All of the list items should evaluate to true
/// </summary>
All,
/// <summary>
/// None of the list items should evaluate to true
/// </summary>
None,
/// <summary>
/// A specific amount of the list items should evaluate to true
/// </summary>
Count
}
}

View File

@ -1,165 +0,0 @@
using System;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
/// <summary>
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
/// </summary>
public class DataModelConditionListPredicate : DataModelConditionPredicate
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
/// </summary>
/// <param name="parent"></param>
/// <param name="predicateType"></param>
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
{
DataModelConditionList = null!;
ApplyParentList();
Initialize();
}
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
: base(parent, entity)
{
DataModelConditionList = null!;
ApplyParentList();
Initialize();
}
/// <summary>
/// Gets the data model condition list this predicate belongs to
/// </summary>
public DataModelConditionList DataModelConditionList { get; private set; }
private void ApplyParentList()
{
DataModelConditionPart? current = Parent;
while (current != null)
{
if (current is DataModelConditionList parentList)
{
DataModelConditionList = parentList;
return;
}
current = current.Parent;
}
if (DataModelConditionList == null)
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
}
private object? GetListPathValue(DataModelPath path, object? target)
{
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
wrapper.UntypedValue = target;
return path.GetValue();
}
#region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath()
{
if (Entity.LeftPath != null)
LeftPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(
DataModelConditionList.ListType,
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
), Entity.LeftPath)
: null;
}
/// <inheritdoc />
protected override void InitializeRightPath()
{
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{
// Right side dynamic inside the list
if (Entity.RightPath.WrapperType == PathWrapperType.List)
{
RightPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(
DataModelConditionList.ListType,
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
), Entity.RightPath)
: null;
}
// Right side dynamic
else
RightPath = new DataModelPath(null, Entity.RightPath);
}
}
#endregion
#region Modification
/// <inheritdoc />
public override Type? GetPreferredRightSideType()
{
Type? preferredType = Operator?.RightSideType;
Type? leftSideType = DataModelConditionList.IsPrimitiveList
? DataModelConditionList.ListType
: LeftPath?.GetPropertyType();
if (preferredType == null)
return null;
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
preferredType = leftSideType;
return preferredType;
}
#endregion
#region Evaluation
/// <summary>
/// Not supported for list predicates, always returns <c>false</c>
/// </summary>
public override bool Evaluate()
{
return false;
}
internal override bool EvaluateObject(object? target)
{
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false;
// Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
object? leftSideValue = GetListPathValue(LeftPath, target);
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
}
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values
if (PredicateType == ProfileRightSideType.Dynamic)
{
// If the path targets a property inside the list, evaluate on the list path value instead of the right path value
if (RightPath.Target is ListPredicateWrapperDataModel)
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
}
return false;
}
#endregion
}
}

View File

@ -1,40 +0,0 @@
using System;
using Artemis.Core.Modules;
namespace Artemis.Core
{
internal class EventPredicateWrapperDataModel<T> : EventPredicateWrapperDataModel
{
[DataModelProperty(Name = "Event arguments", Description = "The arguments provided when the event triggers")]
public T Arguments => (UntypedArguments is T typedArguments ? typedArguments : default)!;
}
/// <summary>
/// Represents a datamodel that wraps the event arguments of an event
/// </summary>
public abstract class EventPredicateWrapperDataModel : DataModel
{
internal EventPredicateWrapperDataModel()
{
Module = Constants.CorePluginFeature;
}
/// <summary>
/// Gets the last arguments of this event as an object
/// </summary>
[DataModelIgnore]
public object? UntypedArguments { get; internal set; }
/// <summary>
/// Creates a new instance of the <see cref="EventPredicateWrapperDataModel" /> class
/// </summary>
public static EventPredicateWrapperDataModel Create(Type type)
{
object? instance = Activator.CreateInstance(typeof(EventPredicateWrapperDataModel<>).MakeGenericType(type));
if (instance == null)
throw new ArtemisCoreException($"Failed to create an instance of EventPredicateWrapperDataModel<T> for type {type.Name}");
return (EventPredicateWrapperDataModel) instance;
}
}
}

View File

@ -1,59 +0,0 @@
using System;
using System.Reflection;
using Artemis.Core.Modules;
namespace Artemis.Core
{
internal class ListPredicateWrapperDataModel<T> : ListPredicateWrapperDataModel
{
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
}
/// <summary>
/// Represents a datamodel that wraps a value in a list
/// </summary>
public abstract class ListPredicateWrapperDataModel : DataModel
{
internal ListPredicateWrapperDataModel()
{
Module = Constants.CorePluginFeature;
}
/// <summary>
/// Gets or sets the value of this list as an object
/// </summary>
[DataModelIgnore]
public object? UntypedValue { get; set; }
/// <summary>
/// Gets or sets the name of the list item
/// </summary>
[DataModelIgnore]
public string? ItemName { get; set; }
#region Overrides of DataModel
/// <inheritdoc />
public override DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
{
if (!string.IsNullOrWhiteSpace(ItemName))
return new DataModelPropertyAttribute {Name = ItemName};
return base.GetPropertyDescription(propertyInfo);
}
#endregion
/// <summary>
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel" /> class
/// </summary>
public static ListPredicateWrapperDataModel Create(Type type, string? name = null)
{
ListPredicateWrapperDataModel? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type)) as ListPredicateWrapperDataModel;
if (instance == null)
throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel<T> for type {type.Name}");
instance.ItemName = name;
return instance;
}
}
}

View File

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

View File

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

View File

@ -1,121 +0,0 @@
using System;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBindingCondition<TLayerProperty, TProperty> : IDataBindingCondition
{
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="DataBindingCondition{TLayerProperty,TProperty}" /> class
/// </summary>
/// <param name="conditionalDataBinding">The conditional data binding this condition is applied too</param>
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding)
{
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
Order = conditionalDataBinding.Conditions.Count + 1;
Condition = new DataModelConditionGroup(null);
Value = default!;
Entity = new DataBindingConditionEntity();
Save();
}
internal DataBindingCondition(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding, DataBindingConditionEntity entity)
{
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
Entity = entity;
Condition = null!;
Value = default!;
Load();
}
/// <summary>
/// Gets the conditional data binding this condition is applied to
/// </summary>
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
/// <summary>
/// Gets or sets the position at which the modifier appears on the data binding
/// </summary>
public int Order { get; set; }
/// <summary>
/// Gets or sets the value to be applied when the condition is met
/// </summary>
public TProperty Value { get; set; }
/// <summary>
/// Gets the root group of the condition that must be met
/// </summary>
public DataModelConditionGroup Condition { get; private set; }
internal DataBindingConditionEntity Entity { get; set; }
/// <inheritdoc />
public bool Evaluate()
{
return Condition.Evaluate();
}
/// <inheritdoc />
public void Save()
{
if (_disposed)
throw new ObjectDisposedException("DataBindingCondition");
if (!ConditionalDataBinding.Entity.Values.Contains(Entity))
ConditionalDataBinding.Entity.Values.Add(Entity);
Entity.Condition = Condition.Entity;
Condition.Save();
Entity.Value = CoreJson.SerializeObject(Value);
Entity.Order = Order;
}
/// <inheritdoc />
public void Load()
{
if (_disposed)
throw new ObjectDisposedException("DataBindingCondition");
Condition = Entity.Condition != null
? new DataModelConditionGroup(null, Entity.Condition)
: new DataModelConditionGroup(null);
Value = (Entity.Value == null ? default : CoreJson.DeserializeObject<TProperty>(Entity.Value))!;
Order = Entity.Order;
}
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
Condition.Dispose();
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition and a value inside a <see cref="ConditionalDataBinding{TLayerProperty,TProperty}" />
/// </summary>
public interface IDataBindingCondition : IStorageModel, IDisposable
{
/// <summary>
/// Evaluates the condition
/// </summary>
bool Evaluate();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public interface IDataBindingModeEntity
{
}
}

View File

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

View File

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

View File

@ -77,4 +77,7 @@
<ItemGroup>
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
</ItemGroup>
<ItemGroup>
<Folder Include="Extensions\" />
</ItemGroup>
</Project>

View File

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

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

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

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

View File

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