1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Core - Added XML comments to all remaining public members/methods

Core - Refactored a lot of code for nullable reference types
This commit is contained in:
Robert 2020-11-16 20:16:06 +01:00
parent 61fc96742d
commit a3cd32f6c4
107 changed files with 1150 additions and 649 deletions

View File

@ -13,6 +13,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<DocumentationFile>bin\x64\Debug\Artemis.Core.xml</DocumentationFile> <DocumentationFile>bin\x64\Debug\Artemis.Core.xml</DocumentationFile>
<NoWarn>1701;1702</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -39,9 +39,9 @@ namespace Artemis.Core
if (ValueTypeSetExpression == null) if (ValueTypeSetExpression == null)
return; return;
if (DataBinding.LayerProperty.PropertyDescription.MaxInputValue is float max) if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
value = Math.Min(value, max); value = Math.Min(value, max);
if (DataBinding.LayerProperty.PropertyDescription.MinInputValue is float min) if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
value = Math.Max(value, min); value = Math.Max(value, min);
base.ApplyValue(value); base.ApplyValue(value);

View File

@ -5,6 +5,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public class SKColorDataBindingConverter : DataBindingConverter<SKColor, SKColor> public class SKColorDataBindingConverter : DataBindingConverter<SKColor, SKColor>
{ {
/// <summary>
/// Creates a new instance of the <see cref="SKColorDataBindingConverter" /> class
/// </summary>
public SKColorDataBindingConverter() public SKColorDataBindingConverter()
{ {
SupportsInterpolate = true; SupportsInterpolate = true;

View File

@ -5,7 +5,7 @@ namespace Artemis.Core
internal class CosecantModifierType : DataBindingModifierType<double> internal class CosecantModifierType : DataBindingModifierType<double>
{ {
public override string Name => "Cosecant"; public override string Name => "Cosecant";
public override string Icon => null; public override string? Icon => null;
public override string Category => "Trigonometry"; public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the cosecant"; public override string Description => "Treats the input as an angle and calculates the cosecant";

View File

@ -5,7 +5,7 @@ namespace Artemis.Core
internal class CotangentModifierType : DataBindingModifierType<double> internal class CotangentModifierType : DataBindingModifierType<double>
{ {
public override string Name => "Cotangent"; public override string Name => "Cotangent";
public override string Icon => null; public override string? Icon => null;
public override string Category => "Trigonometry"; public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the cotangent"; public override string Description => "Treats the input as an angle and calculates the cotangent";

View File

@ -5,7 +5,7 @@ namespace Artemis.Core
internal class SecantModifierType : DataBindingModifierType<double> internal class SecantModifierType : DataBindingModifierType<double>
{ {
public override string Name => "Secant"; public override string Name => "Secant";
public override string Icon => null; public override string? Icon => null;
public override string Category => "Trigonometry"; public override string Category => "Trigonometry";
public override string Description => "Treats the input as an angle and calculates the secant"; public override string Description => "Treats the input as an angle and calculates the secant";

View File

@ -25,7 +25,7 @@
throw new ArtemisCoreException("Color Gradients do not support keyframes."); throw new ArtemisCoreException("Color Gradients do not support keyframes.");
} }
private void OnCurrentValueSet(object sender, LayerPropertyEventArgs<ColorGradient> e) private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs<ColorGradient> e)
{ {
// Don't allow color gradients to be null // Don't allow color gradients to be null
if (BaseValue == null) if (BaseValue == null)

View File

@ -27,8 +27,8 @@
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
float diff = NextKeyframe.Value - CurrentKeyframe.Value; float diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
CurrentValue = CurrentKeyframe.Value + diff * keyframeProgressEased; CurrentValue = CurrentKeyframe!.Value + diff * keyframeProgressEased;
} }
} }
} }

View File

@ -14,15 +14,15 @@
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
float startDiff = NextKeyframe.Value.Start - CurrentKeyframe.Value.Start; float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
float endDiff = NextKeyframe.Value.End - CurrentKeyframe.Value.End; float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
CurrentValue = new FloatRange( CurrentValue = new FloatRange(
(int) (CurrentKeyframe.Value.Start + startDiff * keyframeProgressEased), (int) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
(int) (CurrentKeyframe.Value.End + endDiff * keyframeProgressEased) (int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
); );
} }
private void OnCurrentValueSet(object sender, LayerPropertyEventArgs<FloatRange> e) private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs<FloatRange> e)
{ {
// Don't allow the int range to be null // Don't allow the int range to be null
BaseValue ??= DefaultValue ?? new FloatRange(0, 0); BaseValue ??= DefaultValue ?? new FloatRange(0, 0);

View File

@ -37,8 +37,8 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
int diff = NextKeyframe.Value - CurrentKeyframe.Value; int diff = NextKeyframe!.Value - CurrentKeyframe!.Value;
CurrentValue = (int) Math.Round(CurrentKeyframe.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero); CurrentValue = (int) Math.Round(CurrentKeyframe!.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero);
} }
} }
} }

View File

@ -14,15 +14,15 @@
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
float startDiff = NextKeyframe.Value.Start - CurrentKeyframe.Value.Start; float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
float endDiff = NextKeyframe.Value.End - CurrentKeyframe.Value.End; float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
CurrentValue = new IntRange( CurrentValue = new IntRange(
(int) (CurrentKeyframe.Value.Start + startDiff * keyframeProgressEased), (int) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
(int) (CurrentKeyframe.Value.End + endDiff * keyframeProgressEased) (int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
); );
} }
private void OnCurrentValueSet(object sender, LayerPropertyEventArgs<IntRange> e) private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs<IntRange> e)
{ {
// Don't allow the int range to be null // Don't allow the int range to be null
BaseValue ??= DefaultValue ?? new IntRange(0, 0); BaseValue ??= DefaultValue ?? new IntRange(0, 0);

View File

@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// A special layer property used to configure the selected layer brush /// A special layer property used to configure the selected layer brush
/// </summary> /// </summary>
public class LayerBrushReferenceLayerProperty : LayerProperty<LayerBrushReference> public class LayerBrushReferenceLayerProperty : LayerProperty<LayerBrushReference?>
{ {
internal LayerBrushReferenceLayerProperty() internal LayerBrushReferenceLayerProperty()
{ {
@ -14,7 +14,7 @@
/// <summary> /// <summary>
/// Implicitly converts an <see cref="LayerBrushReferenceLayerProperty" /> to an <see cref="LayerBrushReference" /> /// Implicitly converts an <see cref="LayerBrushReferenceLayerProperty" /> to an <see cref="LayerBrushReference" />
/// </summary> /// </summary>
public static implicit operator LayerBrushReference(LayerBrushReferenceLayerProperty p) public static implicit operator LayerBrushReference?(LayerBrushReferenceLayerProperty p)
{ {
return p.CurrentValue; return p.CurrentValue;
} }

View File

@ -23,7 +23,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
CurrentValue = CurrentKeyframe.Value.Interpolate(NextKeyframe.Value, keyframeProgressEased); CurrentValue = CurrentKeyframe!.Value.Interpolate(NextKeyframe!.Value, keyframeProgressEased);
} }
} }
} }

View File

@ -22,9 +22,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
float xDiff = NextKeyframe.Value.X - CurrentKeyframe.Value.X; float xDiff = NextKeyframe!.Value.X - CurrentKeyframe!.Value.X;
float yDiff = NextKeyframe.Value.Y - CurrentKeyframe.Value.Y; float yDiff = NextKeyframe!.Value.Y - CurrentKeyframe!.Value.Y;
CurrentValue = new SKPoint(CurrentKeyframe.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe.Value.Y + yDiff * keyframeProgressEased); CurrentValue = new SKPoint(CurrentKeyframe!.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe!.Value.Y + yDiff * keyframeProgressEased);
} }
} }
} }

View File

@ -22,9 +22,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
float widthDiff = NextKeyframe.Value.Width - CurrentKeyframe.Value.Width; float widthDiff = NextKeyframe!.Value.Width - CurrentKeyframe!.Value.Width;
float heightDiff = NextKeyframe.Value.Height - CurrentKeyframe.Value.Height; float heightDiff = NextKeyframe!.Value.Height - CurrentKeyframe!.Value.Height;
CurrentValue = new SKSize(CurrentKeyframe.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe.Value.Height + heightDiff * keyframeProgressEased); CurrentValue = new SKSize(CurrentKeyframe!.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe!.Value.Height + heightDiff * keyframeProgressEased);
} }
} }
} }

View File

@ -12,6 +12,9 @@ namespace Artemis.Core
Surface = surface; Surface = surface;
} }
/// <summary>
/// Gets the active surface at the time the event fired
/// </summary>
public ArtemisSurface Surface { get; } public ArtemisSurface Surface { get; }
} }
} }

View File

@ -13,6 +13,9 @@ namespace Artemis.Core
Device = device; Device = device;
} }
/// <summary>
/// Gets the device this event is related to
/// </summary>
public IRGBDevice Device { get; } public IRGBDevice Device { get; }
} }
} }

View File

@ -14,7 +14,14 @@ namespace Artemis.Core
Key = key; Key = key;
} }
/// <summary>
/// Gets the dynamic data model
/// </summary>
public DataModel DynamicDataModel { get; } public DataModel DynamicDataModel { get; }
/// <summary>
/// Gets the key of the dynamic data model on the parent <see cref="DataModel" />
/// </summary>
public string Key { get; } public string Key { get; }
} }
} }

View File

@ -14,7 +14,14 @@ namespace Artemis.Core
RgbSurface = rgbSurface; RgbSurface = rgbSurface;
} }
/// <summary>
/// Gets the bitmap brush used to render this frame
/// </summary>
public BitmapBrush BitmapBrush { get; } public BitmapBrush BitmapBrush { get; }
/// <summary>
/// Gets the RGB surface used to render this frame
/// </summary>
public RGBSurface RgbSurface { get; } public RGBSurface RgbSurface { get; }
} }
} }

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Core.Modules;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
@ -11,17 +9,26 @@ namespace Artemis.Core
/// </summary> /// </summary>
public class FrameRenderingEventArgs : EventArgs public class FrameRenderingEventArgs : EventArgs
{ {
internal FrameRenderingEventArgs(List<Module> modules, SKCanvas canvas, double deltaTime, RGBSurface rgbSurface) internal FrameRenderingEventArgs(SKCanvas canvas, double deltaTime, RGBSurface rgbSurface)
{ {
Modules = modules;
Canvas = canvas; Canvas = canvas;
DeltaTime = deltaTime; DeltaTime = deltaTime;
RgbSurface = rgbSurface; RgbSurface = rgbSurface;
} }
public List<Module> Modules { get; } /// <summary>
/// Gets the canvas this frame is rendering on
/// </summary>
public SKCanvas Canvas { get; } public SKCanvas Canvas { get; }
/// <summary>
/// Gets the delta time since the last frame was rendered
/// </summary>
public double DeltaTime { get; } public double DeltaTime { get; }
/// <summary>
/// Gets the RGB surface used to render this frame
/// </summary>
public RGBSurface RgbSurface { get; } public RGBSurface RgbSurface { get; }
} }
} }

View File

@ -2,17 +2,19 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Provides data about plugin related events
/// </summary>
public class PluginEventArgs : EventArgs public class PluginEventArgs : EventArgs
{ {
public PluginEventArgs() internal PluginEventArgs(Plugin plugin)
{
}
public PluginEventArgs(Plugin plugin)
{ {
Plugin = plugin; Plugin = plugin;
} }
/// <summary>
/// Gets the plugin this event is related to
/// </summary>
public Plugin Plugin { get; } public Plugin Plugin { get; }
} }
} }

View File

@ -2,17 +2,19 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Provides data about plugin feature related events
/// </summary>
public class PluginFeatureEventArgs : EventArgs public class PluginFeatureEventArgs : EventArgs
{ {
public PluginFeatureEventArgs() internal PluginFeatureEventArgs(PluginFeature pluginFeature)
{
}
public PluginFeatureEventArgs(PluginFeature pluginFeature)
{ {
PluginFeature = pluginFeature; PluginFeature = pluginFeature;
} }
/// <summary>
/// Gets the plugin feature this event is related to
/// </summary>
public PluginFeature PluginFeature { get; } public PluginFeature PluginFeature { get; }
} }
} }

View File

@ -2,9 +2,13 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Provides data for the <see langword='DataBindingPropertyUpdatedEvent' /> event.
/// </summary>
/// <typeparam name="T"></typeparam>
public class DataBindingPropertyUpdatedEvent<T> : EventArgs public class DataBindingPropertyUpdatedEvent<T> : EventArgs
{ {
public DataBindingPropertyUpdatedEvent(T value) internal DataBindingPropertyUpdatedEvent(T value)
{ {
Value = value; Value = value;
} }

View File

@ -2,23 +2,35 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Provides strongly typed data for layer property events of type <typeparamref name="T" />.
/// </summary>
public class LayerPropertyEventArgs<T> : EventArgs public class LayerPropertyEventArgs<T> : EventArgs
{ {
public LayerPropertyEventArgs(LayerProperty<T> layerProperty) internal LayerPropertyEventArgs(LayerProperty<T> layerProperty)
{ {
LayerProperty = layerProperty; LayerProperty = layerProperty;
} }
/// <summary>
/// Gets the layer property this event is related to
/// </summary>
public LayerProperty<T> LayerProperty { get; } public LayerProperty<T> LayerProperty { get; }
} }
/// <summary>
/// Provides data for layer property events.
/// </summary>
public class LayerPropertyEventArgs : EventArgs public class LayerPropertyEventArgs : EventArgs
{ {
public LayerPropertyEventArgs(ILayerProperty layerProperty) internal LayerPropertyEventArgs(ILayerProperty layerProperty)
{ {
LayerProperty = layerProperty; LayerProperty = layerProperty;
} }
/// <summary>
/// Gets the layer property this event is related to
/// </summary>
public ILayerProperty LayerProperty { get; } public ILayerProperty LayerProperty { get; }
} }
} }

View File

@ -1,14 +0,0 @@
using System;
namespace Artemis.Core
{
public class LayerPropertyGroupUpdatingEventArgs : EventArgs
{
public LayerPropertyGroupUpdatingEventArgs(double deltaTime)
{
DeltaTime = deltaTime;
}
public double DeltaTime { get; }
}
}

View File

@ -2,31 +2,37 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// An exception thrown when a plugin-related error occurs
/// </summary>
public class ArtemisPluginException : Exception public class ArtemisPluginException : Exception
{ {
public ArtemisPluginException(Plugin plugin) internal ArtemisPluginException(Plugin plugin)
{ {
Plugin = plugin; Plugin = plugin;
} }
public ArtemisPluginException(Plugin plugin, string message) : base(message) internal ArtemisPluginException(Plugin plugin, string message) : base(message)
{ {
Plugin = plugin; Plugin = plugin;
} }
public ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner) internal ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner)
{ {
Plugin = plugin; Plugin = plugin;
} }
public ArtemisPluginException(string message) : base(message) internal ArtemisPluginException(string message) : base(message)
{ {
} }
public ArtemisPluginException(string message, Exception inner) : base(message, inner) internal ArtemisPluginException(string message, Exception inner) : base(message, inner)
{ {
} }
public Plugin Plugin { get; } /// <summary>
/// Gets the plugin the error is related to
/// </summary>
public Plugin? Plugin { get; }
} }
} }

View File

@ -2,23 +2,29 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// An exception thrown when a plugin feature-related error occurs
/// </summary>
public class ArtemisPluginFeatureException : Exception public class ArtemisPluginFeatureException : Exception
{ {
internal ArtemisPluginFeatureException(PluginFeature pluginFeature)
{
PluginFeature = pluginFeature;
}
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message) : base(message)
{
PluginFeature = pluginFeature;
}
internal ArtemisPluginFeatureException(PluginFeature pluginFeature, string message, Exception inner) : base(message, inner)
{
PluginFeature = pluginFeature;
}
/// <summary>
/// Gets the plugin feature the error is related to
/// </summary>
public PluginFeature PluginFeature { get; } public PluginFeature PluginFeature { get; }
public ArtemisPluginFeatureException(PluginFeature pluginFeature)
{
PluginFeature = pluginFeature;
}
public ArtemisPluginFeatureException(PluginFeature pluginFeature, string message) : base(message)
{
PluginFeature = pluginFeature;
}
public ArtemisPluginFeatureException(PluginFeature pluginFeature, string message, Exception inner) : base(message, inner)
{
PluginFeature = pluginFeature;
}
} }
} }

View File

@ -2,13 +2,16 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// An exception thrown when a plugin lock file error occurs
/// </summary>
public class ArtemisPluginLockException : Exception public class ArtemisPluginLockException : Exception
{ {
public ArtemisPluginLockException(Exception innerException) : base(CreateExceptionMessage(innerException), innerException) internal ArtemisPluginLockException(Exception? innerException) : base(CreateExceptionMessage(innerException), innerException)
{ {
} }
private static string CreateExceptionMessage(Exception innerException) private static string CreateExceptionMessage(Exception? innerException)
{ {
return innerException != null return innerException != null
? "Found a lock file, skipping load, see inner exception for last known exception." ? "Found a lock file, skipping load, see inner exception for last known exception."

View File

@ -2,7 +2,7 @@
namespace Artemis.Core namespace Artemis.Core
{ {
public static class DirectoryInfoExtensions internal static class DirectoryInfoExtensions
{ {
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target) public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
{ {

View File

@ -3,8 +3,16 @@ using System.Runtime.CompilerServices;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// A static class providing <see cref="double" /> extensions
/// </summary>
public static class DoubleExtensions public static class DoubleExtensions
{ {
/// <summary>
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
/// </summary>
/// <param name="number">The number to round</param>
/// <returns>The rounded number as an integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(this double number) public static int RoundToInt(this double number)
{ {

View File

@ -1,9 +1,19 @@
using System; using System;
using System.Runtime.CompilerServices;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// A static class providing <see cref="float" /> extensions
/// </summary>
public static class FloatExtensions public static class FloatExtensions
{ {
/// <summary>
/// Rounds the provided number away to zero and casts the result to an <see cref="int" />
/// </summary>
/// <param name="number">The number to round</param>
/// <returns>The rounded number as an integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(this float number) public static int RoundToInt(this float number)
{ {
return (int) Math.Round(number, MidpointRounding.AwayFromZero); return (int) Math.Round(number, MidpointRounding.AwayFromZero);

View File

@ -24,6 +24,10 @@ using System.Collections.Generic;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// A static class providing <see cref="IEnumerable{T}" /> extensions
/// </summary>
// ReSharper disable once InconsistentNaming
public static class IEnumerableExtensions public static class IEnumerableExtensions
{ {
/// <summary> /// <summary>
@ -46,7 +50,7 @@ namespace Artemis.Core
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector) Func<TSource, TKey> keySelector)
{ {
return source.DistinctBy(keySelector, null); return source.DistinctBy(keySelector, null!);
} }
/// <summary> /// <summary>

View File

@ -5,8 +5,16 @@ using System.Text;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// A static class providing <see cref="Process" /> extensions
/// </summary>
public static class ProcessExtensions public static class ProcessExtensions
{ {
/// <summary>
/// Gets the file name of the given process
/// </summary>
/// <param name="p">The process</param>
/// <returns>The filename of the given process</returns>
public static string GetProcessFilename(this Process p) public static string GetProcessFilename(this Process p)
{ {
int capacity = 2000; int capacity = 2000;
@ -17,7 +25,7 @@ namespace Artemis.Core
return builder.ToString(); return builder.ToString();
} }
[DllImport("kernel32.dll")] [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize); private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]

View File

@ -3,7 +3,7 @@ using RGB.NET.Core;
namespace Artemis.Core namespace Artemis.Core
{ {
public static class RgbDeviceExtensions internal static class RgbDeviceExtensions
{ {
public static string GetDeviceIdentifier(this IRGBDevice rgbDevice) public static string GetDeviceIdentifier(this IRGBDevice rgbDevice)
{ {

View File

@ -1,18 +0,0 @@
using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.Core
{
public static class RgbRectangleExtensions
{
public static SKRect ToSKRect(this Rectangle rectangle, double scale)
{
return SKRect.Create(
(rectangle.Location.X * scale).RoundToInt(),
(rectangle.Location.Y * scale).RoundToInt(),
(rectangle.Size.Width * scale).RoundToInt(),
(rectangle.Size.Height * scale).RoundToInt()
);
}
}
}

View File

@ -4,14 +4,28 @@ using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
// ReSharper disable once InconsistentNaming - I didn't come up with SKColor /// <summary>
/// A static class providing <see cref="SKColor" /> extensions
/// </summary>
public static class SKColorExtensions public static class SKColorExtensions
{ {
/// <summary>
/// Converts hte SKColor to an RGB.NET color
/// </summary>
/// <param name="color">The color to convert</param>
/// <returns>The RGB.NET color</returns>
public static Color ToRgbColor(this SKColor color) public static Color ToRgbColor(this SKColor color)
{ {
return new Color(color.Alpha, color.Red, color.Green, color.Blue); return new Color(color.Alpha, color.Red, color.Green, color.Blue);
} }
/// <summary>
/// Interpolates a color between the <paramref name="from" /> and <paramref name="to" /> color.
/// </summary>
/// <param name="from">The first color</param>
/// <param name="to">The second color</param>
/// <param name="progress">A value between 0 and 1</param>
/// <returns>The interpolated color</returns>
public static SKColor Interpolate(this SKColor from, SKColor to, float progress) public static SKColor Interpolate(this SKColor from, SKColor to, float progress)
{ {
int redDiff = to.Red - from.Red; int redDiff = to.Red - from.Red;
@ -27,6 +41,12 @@ namespace Artemis.Core
); );
} }
/// <summary>
/// Adds the two colors together
/// </summary>
/// <param name="a">The first color</param>
/// <param name="b">The second color</param>
/// <returns>The sum of the two colors</returns>
public static SKColor Sum(this SKColor a, SKColor b) public static SKColor Sum(this SKColor a, SKColor b)
{ {
return new SKColor( return new SKColor(

View File

@ -6,6 +6,9 @@ using Humanizer;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// A static class providing <see cref="Type" /> extensions
/// </summary>
public static class TypeExtensions public static class TypeExtensions
{ {
private static readonly Dictionary<Type, List<Type>> PrimitiveTypeConversions = new Dictionary<Type, List<Type>> private static readonly Dictionary<Type, List<Type>> PrimitiveTypeConversions = new Dictionary<Type, List<Type>>
@ -40,6 +43,12 @@ namespace Artemis.Core
{typeof(string), "string"} {typeof(string), "string"}
}; };
/// <summary>
/// Determines whether the provided type is of a specified generic type
/// </summary>
/// <param name="type">The type to check</param>
/// <param name="genericType">The generic type to match</param>
/// <returns>True if the <paramref name="type" /> is generic and of generic type <paramref name="genericType" /></returns>
public static bool IsGenericType(this Type type, Type genericType) public static bool IsGenericType(this Type type, Type genericType)
{ {
if (type == null) if (type == null)
@ -48,11 +57,21 @@ namespace Artemis.Core
return type.BaseType?.GetGenericTypeDefinition() == genericType; return type.BaseType?.GetGenericTypeDefinition() == genericType;
} }
public static bool IsStruct(this Type source) /// <summary>
/// Determines whether the provided type is a struct
/// </summary>
/// <param name="type">The type to check</param>
/// <returns><see langword="true" /> if the type is a struct, otherwise <see langword="false" /></returns>
public static bool IsStruct(this Type type)
{ {
return source.IsValueType && !source.IsPrimitive && !source.IsEnum; return type.IsValueType && !type.IsPrimitive && !type.IsEnum;
} }
/// <summary>
/// Determines whether the provided type is any kind of numeric type
/// </summary>
/// <param name="type">The type to check</param>
/// <returns><see langword="true" /> if the type a numeric type, otherwise <see langword="false" /></returns>
public static bool TypeIsNumber(this Type type) public static bool TypeIsNumber(this Type type)
{ {
return type == typeof(sbyte) return type == typeof(sbyte)
@ -68,6 +87,11 @@ namespace Artemis.Core
|| type == typeof(decimal); || type == typeof(decimal);
} }
/// <summary>
/// Determines whether the provided value is any kind of numeric type
/// </summary>
/// <param name="value">The value to check</param>
/// <returns><see langword="true" /> if the value is of a numeric type, otherwise <see langword="false" /></returns>
public static bool IsNumber(this object value) public static bool IsNumber(this object value)
{ {
return value is sbyte return value is sbyte
@ -126,7 +150,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Returns the default value of the given type /// Returns the default value of the given type
/// </summary> /// </summary>
public static object GetDefault(this Type type) public static object? GetDefault(this Type type)
{ {
return type.IsValueType ? Activator.CreateInstance(type) : null; return type.IsValueType ? Activator.CreateInstance(type) : null;
} }
@ -157,7 +181,7 @@ namespace Artemis.Core
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
return type.GenericTypeArguments[0]; return type.GenericTypeArguments[0];
Type enumerableType = type.GetInterfaces().FirstOrDefault(x => Type? enumerableType = type.GetInterfaces().FirstOrDefault(x =>
x.IsGenericType && x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); x.GetGenericTypeDefinition() == typeof(IEnumerable<>));

View File

@ -18,7 +18,9 @@ namespace Artemis.Core.JsonConverters
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer) public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
{ {
JValue jsonValue = serializer.Deserialize<JValue>(reader); JValue? jsonValue = serializer.Deserialize<JValue>(reader);
if (jsonValue == null)
throw new FormatException();
if (jsonValue.Type == JTokenType.Float) if (jsonValue.Type == JTokenType.Float)
return (int) Math.Round(jsonValue.Value<double>()); return (int) Math.Round(jsonValue.Value<double>());

View File

@ -13,7 +13,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs when a property value changes. /// Occurs when a property value changes.
/// </summary> /// </summary>
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
#endregion #endregion

View File

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Artemis.Core.Properties;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
@ -11,7 +8,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// A gradient containing a list of <see cref="ColorGradientStop" />s /// A gradient containing a list of <see cref="ColorGradientStop" />s
/// </summary> /// </summary>
public class ColorGradient : INotifyPropertyChanged public class ColorGradient : CorePropertyChanged
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ColorGradient" /> class /// Creates a new instance of the <see cref="ColorGradient" /> class
@ -92,7 +89,8 @@ namespace Artemis.Core
ColorGradientStop[] stops = Stops.OrderBy(x => x.Position).ToArray(); ColorGradientStop[] stops = Stops.OrderBy(x => x.Position).ToArray();
if (position <= 0) return stops[0].Color; if (position <= 0) return stops[0].Color;
if (position >= 1) return stops[^1].Color; if (position >= 1) return stops[^1].Color;
ColorGradientStop left = stops[0], right = null; ColorGradientStop left = stops[0];
ColorGradientStop? right = null;
foreach (ColorGradientStop stop in stops) foreach (ColorGradientStop stop in stops)
{ {
if (stop.Position >= position) if (stop.Position >= position)
@ -130,18 +128,5 @@ namespace Artemis.Core
return gradient; return gradient;
} }
#region PropertyChanged
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
internal virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
} }
} }

View File

@ -1,14 +1,11 @@
using System.ComponentModel; using SkiaSharp;
using System.Runtime.CompilerServices;
using Artemis.Core.Properties;
using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary> /// <summary>
/// A color with a position, usually contained in a <see cref="ColorGradient" /> /// A color with a position, usually contained in a <see cref="ColorGradient" />
/// </summary> /// </summary>
public class ColorGradientStop : INotifyPropertyChanged public class ColorGradientStop : CorePropertyChanged
{ {
private SKColor _color; private SKColor _color;
private float _position; private float _position;
@ -28,12 +25,7 @@ namespace Artemis.Core
public SKColor Color public SKColor Color
{ {
get => _color; get => _color;
set set => SetAndNotify(ref _color, value);
{
if (value.Equals(_color)) return;
_color = value;
OnPropertyChanged();
}
} }
/// <summary> /// <summary>
@ -42,25 +34,7 @@ namespace Artemis.Core
public float Position public float Position
{ {
get => _position; get => _position;
set set => SetAndNotify(ref _position, value);
{
if (value.Equals(_position)) return;
_position = value;
OnPropertyChanged();
} }
} }
#region PropertyChanged
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
} }

View File

@ -25,7 +25,7 @@ namespace Artemis.Core
/// Gets the plugin this condition operator belongs to /// Gets the plugin this condition operator belongs to
/// <para>Note: Not set until after registering</para> /// <para>Note: Not set until after registering</para>
/// </summary> /// </summary>
public Plugin Plugin { get; internal set; } public Plugin? Plugin { get; internal set; }
/// <summary> /// <summary>
/// Gets the left side type of this condition operator /// Gets the left side type of this condition operator
@ -64,9 +64,19 @@ namespace Artemis.Core
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue); internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
} }
/// <summary>
/// Represents a side of a condition parameter
/// </summary>
public enum ConditionParameterSide public enum ConditionParameterSide
{ {
/// <summary>
/// The left side of a condition parameter
/// </summary>
Left, Left,
/// <summary>
/// The right side of a condition parameter
/// </summary>
Right Right
} }
} }

View File

@ -76,7 +76,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="target"></param> /// <param name="target"></param>
/// <returns></returns> /// <returns></returns>
internal abstract bool EvaluateObject(object target); internal abstract bool EvaluateObject(object? target);
internal abstract void Save(); internal abstract void Save();
internal abstract DataModelConditionPartEntity GetEntity(); internal abstract DataModelConditionPartEntity GetEntity();
@ -104,14 +104,27 @@ namespace Artemis.Core
#region Events #region Events
public event EventHandler ChildAdded; /// <summary>
public event EventHandler ChildRemoved; /// 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() protected virtual void OnChildAdded()
{ {
ChildAdded?.Invoke(this, EventArgs.Empty); ChildAdded?.Invoke(this, EventArgs.Empty);
} }
/// <summary>
/// Invokers the <see cref="ChildRemoved" /> event
/// </summary>
protected virtual void OnChildRemoved() protected virtual void OnChildRemoved()
{ {
ChildRemoved?.Invoke(this, EventArgs.Empty); ChildRemoved?.Invoke(this, EventArgs.Empty);

View File

@ -63,8 +63,8 @@ namespace Artemis.Core
public override string ToString() public override string ToString()
{ {
if (PredicateType == ProfileRightSideType.Dynamic) if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}"; return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}"; return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}";
} }
#region IDisposable #region IDisposable
@ -138,7 +138,14 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Initializes the left path of this condition predicate
/// </summary>
protected abstract void InitializeLeftPath(); protected abstract void InitializeLeftPath();
/// <summary>
/// Initializes the right path of this condition predicate
/// </summary>
protected abstract void InitializeRightPath(); protected abstract void InitializeRightPath();
#endregion #endregion
@ -315,7 +322,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
return false; return false;
} }
@ -344,7 +351,7 @@ namespace Artemis.Core
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
if (Operator != null) if (Operator?.Plugin != null)
{ {
Entity.OperatorPluginGuid = Operator.Plugin.Guid; Entity.OperatorPluginGuid = Operator.Plugin.Guid;
Entity.OperatorType = Operator.GetType().Name; Entity.OperatorType = Operator.GetType().Name;
@ -358,7 +365,7 @@ namespace Artemis.Core
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
{ {
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator; BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.Plugin.Guid && Entity.OperatorType == conditionOperator.GetType().Name) if (Entity.OperatorPluginGuid == conditionOperator.Plugin!.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }

View File

@ -11,9 +11,9 @@ namespace Artemis.Core
public class DataModelConditionEvent : DataModelConditionPart public class DataModelConditionEvent : DataModelConditionPart
{ {
private bool _disposed; private bool _disposed;
private bool _reinitializing;
private IDataModelEvent? _event; private IDataModelEvent? _event;
private bool _eventTriggered; private bool _eventTriggered;
private bool _reinitializing;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class /// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
@ -40,7 +40,10 @@ namespace Artemis.Core
/// </summary> /// </summary>
public DataModelPath? EventPath { get; private set; } public DataModelPath? EventPath { get; private set; }
public Type? EventArgumentType { get; set; } /// <summary>
/// Gets or sets the type of argument the event provides
/// </summary>
public Type? EventArgumentType { get; private set; }
internal DataModelConditionEventEntity Entity { get; set; } internal DataModelConditionEventEntity Entity { get; set; }
@ -62,22 +65,12 @@ namespace Artemis.Core
// If there is a child (root group), it must evaluate to true whenever the event triggered // If there is a child (root group), it must evaluate to true whenever the event triggered
if (Children.Any()) if (Children.Any())
return Children[0].EvaluateObject(_event.LastEventArgumentsUntyped); return Children[0].EvaluateObject(_event?.LastEventArgumentsUntyped);
// If there are no children, we always evaluate to true whenever the event triggered // If there are no children, we always evaluate to true whenever the event triggered
return true; return true;
} }
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
{
if (_event != null)
_event.EventTriggered -= OnEventTriggered;
_event = dataModelEvent;
if (_event != null)
_event.EventTriggered += OnEventTriggered;
}
/// <summary> /// <summary>
/// Updates the event the condition is triggered by /// Updates the event the condition is triggered by
/// </summary> /// </summary>
@ -108,16 +101,6 @@ namespace Artemis.Core
} }
} }
private Type? GetEventArgumentType()
{
if (EventPath == null || !EventPath.IsValid)
return null;
// 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);
}
#region IDisposable #region IDisposable
/// <inheritdoc /> /// <inheritdoc />
@ -135,7 +118,7 @@ namespace Artemis.Core
#endregion #endregion
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
return false; return false;
} }
@ -191,6 +174,26 @@ namespace Artemis.Core
} }
} }
private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
{
if (_event != null)
_event.EventTriggered -= OnEventTriggered;
_event = dataModelEvent;
if (_event != null)
_event.EventTriggered += OnEventTriggered;
}
private Type? GetEventArgumentType()
{
if (EventPath == null || !EventPath.IsValid)
return null;
// 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 bool PointsToEvent(DataModelPath dataModelPath) private bool PointsToEvent(DataModelPath dataModelPath)
{ {
Type? type = dataModelPath.GetPropertyType(); Type? type = dataModelPath.GetPropertyType();

View File

@ -53,7 +53,7 @@ namespace Artemis.Core
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event"); 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) private object? GetEventPathValue(DataModelPath path, object? target)
{ {
lock (path) lock (path)
{ {
@ -67,6 +67,7 @@ namespace Artemis.Core
#region Initialization #region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath() protected override void InitializeLeftPath()
{ {
if (Entity.LeftPath != null) if (Entity.LeftPath != null)
@ -75,6 +76,7 @@ namespace Artemis.Core
: null; : null;
} }
/// <inheritdoc />
protected override void InitializeRightPath() protected override void InitializeRightPath()
{ {
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
@ -122,7 +124,7 @@ namespace Artemis.Core
return false; return false;
} }
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
if (Operator == null || LeftPath == null || !LeftPath.IsValid) if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false; return false;

View File

@ -46,12 +46,14 @@ namespace Artemis.Core
#region Initialization #region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath() protected override void InitializeLeftPath()
{ {
if (Entity.LeftPath != null) if (Entity.LeftPath != null)
LeftPath = new DataModelPath(null, Entity.LeftPath); LeftPath = new DataModelPath(null, Entity.LeftPath);
} }
/// <inheritdoc />
protected override void InitializeRightPath() protected override void InitializeRightPath()
{ {
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)

View File

@ -19,7 +19,7 @@ namespace Artemis.Core
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class /// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
/// </summary> /// </summary>
/// <param name="parent"></param> /// <param name="parent"></param>
public DataModelConditionGroup(DataModelConditionPart parent) public DataModelConditionGroup(DataModelConditionPart? parent)
{ {
Parent = parent; Parent = parent;
Entity = new DataModelConditionGroupEntity(); Entity = new DataModelConditionGroupEntity();
@ -32,7 +32,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="parent"></param> /// <param name="parent"></param>
/// <param name="entity"></param> /// <param name="entity"></param>
public DataModelConditionGroup(DataModelConditionPart parent, DataModelConditionGroupEntity entity) public DataModelConditionGroup(DataModelConditionPart? parent, DataModelConditionGroupEntity entity)
{ {
Parent = parent; Parent = parent;
Entity = entity; Entity = entity;
@ -124,7 +124,7 @@ namespace Artemis.Core
#endregion #endregion
/// <inheritdoc /> /// <inheritdoc />
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionGroup"); throw new ObjectDisposedException("DataModelConditionGroup");

View File

@ -94,7 +94,7 @@ namespace Artemis.Core
{ {
Type listType = ListPath.GetPropertyType()!; Type listType = ListPath.GetPropertyType()!;
ListType = listType.GetGenericEnumerableType(); ListType = listType.GetGenericEnumerableType();
IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string); IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
// Create a new root group // Create a new root group
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
@ -188,7 +188,7 @@ namespace Artemis.Core
if (ListPath.IsValid) if (ListPath.IsValid)
{ {
ListType = listType.GetGenericEnumerableType(); ListType = listType.GetGenericEnumerableType();
IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string); IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
} }
else else
{ {

View File

@ -53,7 +53,7 @@ namespace Artemis.Core
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list"); 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) private object? GetListPathValue(DataModelPath path, object? target)
{ {
if (!(path.Target is ListPredicateWrapperDataModel wrapper)) if (!(path.Target is ListPredicateWrapperDataModel wrapper))
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target"); throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
@ -64,6 +64,7 @@ namespace Artemis.Core
#region Initialization #region Initialization
/// <inheritdoc />
protected override void InitializeLeftPath() protected override void InitializeLeftPath()
{ {
if (Entity.LeftPath != null) if (Entity.LeftPath != null)
@ -72,6 +73,7 @@ namespace Artemis.Core
: null; : null;
} }
/// <inheritdoc />
protected override void InitializeRightPath() protected override void InitializeRightPath()
{ {
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
@ -121,7 +123,7 @@ namespace Artemis.Core
return false; return false;
} }
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
if (Operator == null || LeftPath == null || !LeftPath.IsValid) if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false; return false;

View File

@ -19,8 +19,11 @@ namespace Artemis.Core
Feature = Constants.CorePluginFeature; Feature = Constants.CorePluginFeature;
} }
/// <summary>
/// Gets the last arguments of this event as an object
/// </summary>
[DataModelIgnore] [DataModelIgnore]
public object? UntypedArguments { get; set; } public object? UntypedArguments { get; internal set; }
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="EventPredicateWrapperDataModel" /> class /// Creates a new instance of the <see cref="EventPredicateWrapperDataModel" /> class

View File

@ -19,6 +19,9 @@ namespace Artemis.Core
Feature = Constants.CorePluginFeature; Feature = Constants.CorePluginFeature;
} }
/// <summary>
/// Gets or sets the value of this list as an object
/// </summary>
[DataModelIgnore] [DataModelIgnore]
public object? UntypedValue { get; set; } public object? UntypedValue { get; set; }

View File

@ -6,10 +6,10 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding public class DataBinding<TLayerProperty, TProperty> : IDataBinding
{ {
private TProperty _currentValue; private TProperty _currentValue = default!;
private TProperty _previousValue = default!;
private bool _disposed; private bool _disposed;
private TimeSpan _easingProgress; private TimeSpan _easingProgress;
private TProperty _previousValue;
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration) internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{ {
@ -34,7 +34,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the data binding registration this data binding is based upon /// Gets the data binding registration this data binding is based upon
/// </summary> /// </summary>
public DataBindingRegistration<TLayerProperty, TProperty> Registration { get; private set; } public DataBindingRegistration<TLayerProperty, TProperty>? Registration { get; private set; }
/// <summary> /// <summary>
/// Gets the layer property this data binding targets /// Gets the layer property this data binding targets
@ -44,12 +44,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the converter used to apply this data binding to the <see cref="LayerProperty" /> /// Gets the converter used to apply this data binding to the <see cref="LayerProperty" />
/// </summary> /// </summary>
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; private set; } public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
/// <summary> /// <summary>
/// Gets the data binding mode /// Gets the data binding mode
/// </summary> /// </summary>
public IDataBindingMode<TLayerProperty, TProperty> DataBindingMode { get; private set; } public IDataBindingMode<TLayerProperty, TProperty>? DataBindingMode { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the easing time of the data binding /// Gets or sets the easing time of the data binding
@ -93,9 +93,9 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Returns the type of the target property of this data binding /// Returns the type of the target property of this data binding
/// </summary> /// </summary>
public Type GetTargetType() public Type? GetTargetType()
{ {
return Registration.PropertyExpression.ReturnType; return Registration?.PropertyExpression.ReturnType;
} }
private void ResetEasing(TProperty value) private void ResetEasing(TProperty value)
@ -111,15 +111,15 @@ namespace Artemis.Core
throw new ArgumentNullException(nameof(dataBindingRegistration)); throw new ArgumentNullException(nameof(dataBindingRegistration));
dataBindingRegistration.DataBinding = this; dataBindingRegistration.DataBinding = this;
Converter = dataBindingRegistration?.Converter; Converter = dataBindingRegistration.Converter;
Registration = dataBindingRegistration; Registration = dataBindingRegistration;
if (GetTargetType().IsValueType) if (GetTargetType()!.IsValueType)
{ {
if (_currentValue == null) if (_currentValue == null)
_currentValue = default; _currentValue = default!;
if (_previousValue == null) if (_previousValue == null)
_previousValue = default; _previousValue = default!;
} }
Converter?.Initialize(this); Converter?.Initialize(this);
@ -127,7 +127,7 @@ namespace Artemis.Core
private TProperty GetInterpolatedValue() private TProperty GetInterpolatedValue()
{ {
if (_easingProgress == EasingTime || !Converter.SupportsInterpolate) if (_easingProgress == EasingTime || Converter == null || !Converter.SupportsInterpolate)
return _currentValue; return _currentValue;
double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds; double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds;
@ -180,6 +180,7 @@ namespace Artemis.Core
{ {
_disposed = true; _disposed = true;
if (Registration != null)
Registration.DataBinding = null; Registration.DataBinding = null;
DataBindingMode?.Dispose(); DataBindingMode?.Dispose();
} }
@ -234,7 +235,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("DataBinding"); throw new ObjectDisposedException("DataBinding");
// General // General
DataBindingRegistration<TLayerProperty, TProperty> registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression); DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
if (registration != null) if (registration != null)
ApplyRegistration(registration); ApplyRegistration(registration);
@ -253,8 +254,10 @@ namespace Artemis.Core
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity)) if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
LayerProperty.Entity.DataBindingEntities.Add(Entity); LayerProperty.Entity.DataBindingEntities.Add(Entity);
// General // Don't save an invalid state
if (Registration != null)
Entity.TargetExpression = Registration.PropertyExpression.ToString(); Entity.TargetExpression = Registration.PropertyExpression.ToString();
Entity.EasingTime = EasingTime; Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction; Entity.EasingFunction = (int) EasingFunction;

View File

@ -13,22 +13,22 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a dynamically compiled getter pointing to the data bound property /// Gets a dynamically compiled getter pointing to the data bound property
/// </summary> /// </summary>
public Func<TLayerProperty, TProperty> GetExpression { get; private set; } public Func<TLayerProperty, TProperty>? GetExpression { get; private set; }
/// <summary> /// <summary>
/// Gets a dynamically compiled setter pointing to the data bound property used for value types /// Gets a dynamically compiled setter pointing to the data bound property used for value types
/// </summary> /// </summary>
public Action<TProperty> ValueTypeSetExpression { get; private set; } public Action<TProperty>? ValueTypeSetExpression { get; private set; }
/// <summary> /// <summary>
/// Gets a dynamically compiled setter pointing to the data bound property used for reference types /// Gets a dynamically compiled setter pointing to the data bound property used for reference types
/// </summary> /// </summary>
public Action<TLayerProperty, TProperty> ReferenceTypeSetExpression { get; private set; } public Action<TLayerProperty, TProperty>? ReferenceTypeSetExpression { get; private set; }
/// <summary> /// <summary>
/// Gets the data binding this converter is applied to /// Gets the data binding this converter is applied to
/// </summary> /// </summary>
public DataBinding<TLayerProperty, TProperty> DataBinding { get; private set; } public DataBinding<TLayerProperty, TProperty>? DataBinding { get; private set; }
/// <summary> /// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Sum" /> method /// Gets whether or not this data binding converter supports the <see cref="Sum" /> method
@ -65,6 +65,8 @@ namespace Artemis.Core
/// <param name="value"></param> /// <param name="value"></param>
public virtual void ApplyValue(TProperty value) public virtual void ApplyValue(TProperty value)
{ {
if (DataBinding == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
if (ReferenceTypeSetExpression != null) if (ReferenceTypeSetExpression != null)
ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value); ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value);
else if (ValueTypeSetExpression != null) else if (ValueTypeSetExpression != null)
@ -76,6 +78,8 @@ namespace Artemis.Core
/// </summary> /// </summary>
public virtual TProperty GetValue() public virtual TProperty GetValue()
{ {
if (DataBinding == null || GetExpression == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
return GetExpression(DataBinding.LayerProperty.CurrentValue); return GetExpression(DataBinding.LayerProperty.CurrentValue);
} }
@ -84,7 +88,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public virtual TProperty ConvertFromObject(object? source) public virtual TProperty ConvertFromObject(object? source)
{ {
return (TProperty) Convert.ChangeType(source, typeof(TProperty)); return (TProperty) Convert.ChangeType(source, typeof(TProperty))!;
} }
/// <summary> /// <summary>
@ -96,6 +100,9 @@ namespace Artemis.Core
internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding) internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding)
{ {
if (dataBinding.Registration == null)
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
DataBinding = dataBinding; DataBinding = dataBinding;
GetExpression = dataBinding.Registration.PropertyExpression.Compile(); GetExpression = dataBinding.Registration.PropertyExpression.Compile();
CreateSetExpression(); CreateSetExpression();
@ -106,14 +113,14 @@ namespace Artemis.Core
private void CreateSetExpression() private void CreateSetExpression()
{ {
// If the registration does not point towards a member of LayerProperty<T>.CurrentValue, assign directly to LayerProperty<T>.CurrentValue // If the registration does not point towards a member of LayerProperty<T>.CurrentValue, assign directly to LayerProperty<T>.CurrentValue
if (DataBinding.Registration.Member == null) if (DataBinding!.Registration?.Member == null)
{ {
CreateSetCurrentValueExpression(); CreateSetCurrentValueExpression();
return; return;
} }
// Ensure the member of LayerProperty<T>.CurrentValue has a setter // Ensure the member of LayerProperty<T>.CurrentValue has a setter
MethodInfo setterMethod = null; MethodInfo? setterMethod = null;
if (DataBinding.Registration.Member is PropertyInfo propertyInfo) if (DataBinding.Registration.Member is PropertyInfo propertyInfo)
setterMethod = propertyInfo.GetSetMethod(); setterMethod = propertyInfo.GetSetMethod();
// If there is no setter, the built-in data binding cannot do its job, stay null // If there is no setter, the built-in data binding cannot do its job, stay null
@ -130,6 +137,9 @@ namespace Artemis.Core
private void CreateSetReferenceTypeExpression() private void CreateSetReferenceTypeExpression()
{ {
if (DataBinding!.Registration?.Member == null)
throw new ArtemisCoreException("Cannot create value setter for data binding without a registration");
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue"); ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue");
MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member); MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member);
@ -141,6 +151,9 @@ namespace Artemis.Core
private void CreateSetValueTypeExpression() private void CreateSetValueTypeExpression()
{ {
if (DataBinding!.Registration?.Member == null)
throw new ArtemisCoreException("Cannot create value setter for data binding without a registration");
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current"); ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current");
ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty); ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty);
@ -161,7 +174,7 @@ namespace Artemis.Core
private void CreateSetCurrentValueExpression() private void CreateSetCurrentValueExpression()
{ {
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty); ConstantExpression layerProperty = Expression.Constant(DataBinding!.LayerProperty);
MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty,
DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]);

View File

@ -38,28 +38,28 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the member the <see cref="PropertyExpression" /> targets /// Gets the member the <see cref="PropertyExpression" /> targets
/// <para><c>null</c> if the <see cref="PropertyExpression" /> is not a member expression</para> /// <para><see langword="null"/> if the <see cref="PropertyExpression" /> is not a member expression</para>
/// </summary> /// </summary>
public MemberInfo Member { get; } public MemberInfo? Member { get; }
/// <summary> /// <summary>
/// Gets the data binding created using this registration /// Gets the data binding created using this registration
/// </summary> /// </summary>
public DataBinding<TLayerProperty, TProperty> DataBinding { get; internal set; } public DataBinding<TLayerProperty, TProperty>? DataBinding { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public IDataBinding GetDataBinding() public IDataBinding? GetDataBinding()
{ {
return DataBinding; return DataBinding;
} }
/// <inheritdoc /> /// <inheritdoc />
public IDataBinding CreateDataBinding() public IDataBinding? CreateDataBinding()
{ {
if (DataBinding != null) if (DataBinding != null)
return DataBinding; return DataBinding;
DataBindingEntity dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString()); DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString());
if (dataBinding == null) if (dataBinding == null)
return null; return null;

View File

@ -8,12 +8,12 @@
/// <summary> /// <summary>
/// Returns the data binding applied using this registration /// Returns the data binding applied using this registration
/// </summary> /// </summary>
public IDataBinding GetDataBinding(); public IDataBinding? GetDataBinding();
/// <summary> /// <summary>
/// If found, creates a data binding from storage /// If found, creates a data binding from storage
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
IDataBinding CreateDataBinding(); IDataBinding? CreateDataBinding();
} }
} }

View File

@ -21,13 +21,13 @@ namespace Artemis.Core
Load(); Load();
} }
internal ConditionalDataBindingEntity Entity { get; }
/// <summary> /// <summary>
/// Gets a list of conditions applied to this data binding /// Gets a list of conditions applied to this data binding
/// </summary> /// </summary>
public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions => _conditions.AsReadOnly(); public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions => _conditions.AsReadOnly();
internal ConditionalDataBindingEntity Entity { get; }
/// <inheritdoc /> /// <inheritdoc />
public DataBinding<TLayerProperty, TProperty> DataBinding { get; } public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
@ -37,9 +37,7 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("ConditionalDataBinding"); throw new ObjectDisposedException("ConditionalDataBinding");
DataBindingCondition<TLayerProperty, TProperty> condition = Conditions.FirstOrDefault(c => c.Evaluate()); DataBindingCondition<TLayerProperty, TProperty>? condition = Conditions.FirstOrDefault(c => c.Evaluate());
if (condition != null)
Console.WriteLine();
return condition == null ? baseValue : condition.Value; return condition == null ? baseValue : condition.Value;
} }
@ -138,8 +136,11 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs when a condition is added or removed /// Occurs when a condition is added or removed
/// </summary> /// </summary>
public event EventHandler ConditionsUpdated; public event EventHandler? ConditionsUpdated;
/// <summary>
/// Invokes the <see cref="ConditionsUpdated" /> event
/// </summary>
protected virtual void OnConditionsUpdated() protected virtual void OnConditionsUpdated()
{ {
ConditionsUpdated?.Invoke(this, EventArgs.Empty); ConditionsUpdated?.Invoke(this, EventArgs.Empty);

View File

@ -18,6 +18,8 @@ namespace Artemis.Core
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding)); ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
Order = conditionalDataBinding.Conditions.Count + 1; Order = conditionalDataBinding.Conditions.Count + 1;
Condition = new DataModelConditionGroup(null); Condition = new DataModelConditionGroup(null);
Value = default!;
Entity = new DataBindingConditionEntity(); Entity = new DataBindingConditionEntity();
Save(); Save();
} }
@ -26,6 +28,9 @@ namespace Artemis.Core
{ {
ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding)); ConditionalDataBinding = conditionalDataBinding ?? throw new ArgumentNullException(nameof(conditionalDataBinding));
Entity = entity; Entity = entity;
Condition = null!;
Value = default!;
Load(); Load();
} }
@ -83,7 +88,7 @@ namespace Artemis.Core
? new DataModelConditionGroup(null, Entity.Condition) ? new DataModelConditionGroup(null, Entity.Condition)
: new DataModelConditionGroup(null); : new DataModelConditionGroup(null);
Value = Entity.Value == null ? default : JsonConvert.DeserializeObject<TProperty>(Entity.Value); Value = (Entity.Value == null ? default : JsonConvert.DeserializeObject<TProperty>(Entity.Value))!;
Order = Entity.Order; Order = Entity.Order;
} }

View File

@ -15,7 +15,7 @@ namespace Artemis.Core
/// Gets the plugin this data binding modifier belongs to /// Gets the plugin this data binding modifier belongs to
/// <para>Note: Not set until after registering</para> /// <para>Note: Not set until after registering</para>
/// </summary> /// </summary>
public Plugin Plugin { get; internal set; } public Plugin? Plugin { get; internal set; }
/// <summary> /// <summary>
/// Gets the value type of this modifier type /// Gets the value type of this modifier type
@ -35,17 +35,17 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets or sets the icon of this modifier /// Gets or sets the icon of this modifier
/// </summary> /// </summary>
public abstract string Icon { get; } public abstract string? Icon { get; }
/// <summary> /// <summary>
/// Gets the description of this modifier /// Gets the description of this modifier
/// </summary> /// </summary>
public virtual string Description => null; public virtual string? Description => null;
/// <summary> /// <summary>
/// Gets the category of this modifier /// Gets the category of this modifier
/// </summary> /// </summary>
public virtual string Category => null; public virtual string? Category => null;
/// <summary> /// <summary>
/// Returns whether the given type is supported by the modifier /// Returns whether the given type is supported by the modifier
@ -69,14 +69,26 @@ namespace Artemis.Core
/// </para> /// </para>
/// </summary> /// </summary>
/// <param name="currentValue">The current value before modification, type should match <see cref="ValueType" /></param> /// <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> /// <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> /// <returns>The modified value, with a type of <see cref="ValueType" /></returns>
internal abstract object? InternalApply(object? currentValue, object? parameterValue); internal abstract object? InternalApply(object? currentValue, object? parameterValue);
} }
/// <summary>
/// Represents a part of a modifier type
/// </summary>
public enum ModifierTypePart public enum ModifierTypePart
{ {
/// <summary>
/// The value part of a modifier, backed by <see cref="DataBindingModifierType{TValue}.ValueType" />
/// </summary>
Value, Value,
/// <summary>
/// The parameter part of a modifier, backed by <see cref="DataBindingModifierType{TValue,TParameter}.ParameterType" />
/// </summary>
Parameter Parameter
} }
} }

View File

@ -103,8 +103,8 @@ namespace Artemis.Core
return; return;
} }
Type targetType = DirectDataBinding.DataBinding.GetTargetType(); Type? targetType = DirectDataBinding.DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType, ModifierTypePart.Value)) if (targetType != null && !modifierType.SupportsType(targetType, ModifierTypePart.Value))
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + 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}"); $"it does not support this data binding's type {targetType.Name}");
@ -158,7 +158,7 @@ namespace Artemis.Core
/// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression /// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression
/// </summary> /// </summary>
/// <param name="staticValue">The static value to use as a parameter</param> /// <param name="staticValue">The static value to use as a parameter</param>
public void UpdateParameterStatic(object staticValue) public void UpdateParameterStatic(object? staticValue)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataBindingModifier"); throw new ObjectDisposedException("DataBindingModifier");
@ -167,10 +167,10 @@ namespace Artemis.Core
ParameterPath?.Dispose(); ParameterPath?.Dispose();
ParameterPath = null; ParameterPath = null;
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); Type? parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
// If not null ensure the types match and if not, convert it // If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == parameterType) if (parameterType == null || staticValue != null && staticValue.GetType() == parameterType)
ParameterStaticValue = staticValue; ParameterStaticValue = staticValue;
else if (staticValue != null) else if (staticValue != null)
ParameterStaticValue = Convert.ChangeType(staticValue, parameterType); ParameterStaticValue = Convert.ChangeType(staticValue, parameterType);
@ -189,7 +189,7 @@ namespace Artemis.Core
// Modifier type // Modifier type
if (Entity.ModifierTypePluginGuid != null && ModifierType == null) if (Entity.ModifierTypePluginGuid != null && ModifierType == null)
{ {
BaseDataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType; BaseDataBindingModifierType? modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
if (modifierType != null) if (modifierType != null)
UpdateModifierType(modifierType); UpdateModifierType(modifierType);
} }
@ -203,17 +203,20 @@ namespace Artemis.Core
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null) else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null)
{ {
// Use the target type so JSON.NET has a better idea what to do // Use the target type so JSON.NET has a better idea what to do
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); Type? parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
object staticValue; object? staticValue = null;
try try
{ {
staticValue = JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType); staticValue = parameterType != null
? JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType)
: JsonConvert.DeserializeObject(Entity.ParameterStaticValue);
} }
// If deserialization fails, use the type's default // If deserialization fails, use the type's default
catch (JsonSerializationException e) catch (JsonSerializationException e)
{ {
DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e); DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e);
if (parameterType != null)
staticValue = Activator.CreateInstance(parameterType); staticValue = Activator.CreateInstance(parameterType);
} }
@ -235,7 +238,7 @@ namespace Artemis.Core
DirectDataBinding.Entity.Modifiers.Add(Entity); DirectDataBinding.Entity.Modifiers.Add(Entity);
// Modifier // Modifier
if (ModifierType != null) if (ModifierType?.Plugin != null)
{ {
Entity.ModifierType = ModifierType.GetType().Name; Entity.ModifierType = ModifierType.GetType().Name;
Entity.ModifierTypePluginGuid = ModifierType.Plugin.Guid; Entity.ModifierTypePluginGuid = ModifierType.Plugin.Guid;
@ -280,17 +283,17 @@ namespace Artemis.Core
#region Event handlers #region Event handlers
private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object sender, DataBindingModifierTypeStoreEvent e) private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object? sender, DataBindingModifierTypeStoreEvent e)
{ {
if (ModifierType != null) if (ModifierType != null)
return; return;
BaseDataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType; BaseDataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType;
if (modifierType.Plugin.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType) if (modifierType.Plugin!.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType)
UpdateModifierType(modifierType); UpdateModifierType(modifierType);
} }
private void DataBindingModifierTypeStoreOnDataBindingModifierRemoved(object sender, DataBindingModifierTypeStoreEvent e) private void DataBindingModifierTypeStoreOnDataBindingModifierRemoved(object? sender, DataBindingModifierTypeStoreEvent e)
{ {
if (e.TypeRegistration.DataBindingModifierType == ModifierType) if (e.TypeRegistration.DataBindingModifierType == ModifierType)
UpdateModifierType(null); UpdateModifierType(null);

View File

@ -42,7 +42,7 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DirectDataBinding"); throw new ObjectDisposedException("DirectDataBinding");
if (SourcePath == null || !SourcePath.IsValid) if (SourcePath == null || !SourcePath.IsValid || DataBinding.Converter == null)
return baseValue; return baseValue;
object? dataBindingValue = SourcePath.GetValue(); object? dataBindingValue = SourcePath.GetValue();
@ -188,6 +188,9 @@ namespace Artemis.Core
/// </summary> /// </summary>
public event EventHandler? ModifiersUpdated; public event EventHandler? ModifiersUpdated;
/// <summary>
/// Invokes the <see cref="ModifiersUpdated" /> event
/// </summary>
protected virtual void OnModifiersUpdated() protected virtual void OnModifiersUpdated()
{ {
ModifiersUpdated?.Invoke(this, EventArgs.Empty); ModifiersUpdated?.Invoke(this, EventArgs.Empty);

View File

@ -318,18 +318,24 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs whenever the path becomes invalid /// Occurs whenever the path becomes invalid
/// </summary> /// </summary>
public event EventHandler PathInvalidated; public event EventHandler? PathInvalidated;
/// <summary> /// <summary>
/// Occurs whenever the path becomes valid /// Occurs whenever the path becomes valid
/// </summary> /// </summary>
public event EventHandler PathValidated; public event EventHandler? PathValidated;
/// <summary>
/// Invokes the <see cref="PathInvalidated" /> event
/// </summary>
protected virtual void OnPathValidated() protected virtual void OnPathValidated()
{ {
PathValidated?.Invoke(this, EventArgs.Empty); PathValidated?.Invoke(this, EventArgs.Empty);
} }
/// <summary>
/// Invokes the <see cref="PathValidated" /> event
/// </summary>
protected virtual void OnPathInvalidated() protected virtual void OnPathInvalidated()
{ {
PathInvalidated?.Invoke(this, EventArgs.Empty); PathInvalidated?.Invoke(this, EventArgs.Empty);

View File

@ -115,8 +115,8 @@ namespace Artemis.Core
// Dynamic types have a data model description // Dynamic types have a data model description
if (Type == DataModelPathSegmentType.Dynamic) if (Type == DataModelPathSegmentType.Dynamic)
return (GetValue() as DataModel)?.DataModelDescription; return (GetValue() as DataModel)?.DataModelDescription;
if (IsStartSegment && DataModelPath.Target is DataModel targetDataModel) if (IsStartSegment && DataModelPath.Target != null)
return targetDataModel.DataModelDescription; return DataModelPath.Target.DataModelDescription;
if (IsStartSegment) if (IsStartSegment)
return null; return null;
@ -125,7 +125,7 @@ namespace Artemis.Core
return null; return null;
// Static types may have one as an attribute // Static types may have one as an attribute
DataModelPropertyAttribute? attribute = (DataModelPropertyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute)); DataModelPropertyAttribute? attribute = (DataModelPropertyAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
if (attribute != null) if (attribute != null)
{ {
if (string.IsNullOrWhiteSpace(attribute.Name)) if (string.IsNullOrWhiteSpace(attribute.Name))
@ -187,8 +187,8 @@ namespace Artemis.Core
return CreateExpression(parameter, expression, nullCondition); return CreateExpression(parameter, expression, nullCondition);
// If a dynamic data model is found the use that // If a dynamic data model is found the use that
bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel dynamicDataModel); bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel? dynamicDataModel);
if (hasDynamicDataModel) if (hasDynamicDataModel && dynamicDataModel != null)
DetermineDynamicType(dynamicDataModel); DetermineDynamicType(dynamicDataModel);
_dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded; _dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded;
@ -219,7 +219,7 @@ namespace Artemis.Core
accessorExpression = Expression.Call( accessorExpression = Expression.Call(
expression, expression,
nameof(DataModel.DynamicChild), nameof(DataModel.DynamicChild),
new[] {DynamicDataModelType}, DynamicDataModelType != null ? new[] {DynamicDataModelType} : null,
Expression.Constant(Identifier) Expression.Constant(Identifier)
); );

View File

@ -30,8 +30,8 @@ namespace Artemis.Core
Name = name; Name = name;
Enabled = true; Enabled = true;
_layerEffects = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
Parent.AddChild(this); Parent.AddChild(this);
} }
@ -47,8 +47,8 @@ namespace Artemis.Core
Enabled = folderEntity.Enabled; Enabled = folderEntity.Enabled;
Order = folderEntity.Order; Order = folderEntity.Order;
_layerEffects = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
Load(); Load();
} }
@ -58,11 +58,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
public bool IsRootFolder => Parent == Profile; public bool IsRootFolder => Parent == Profile;
/// <summary>
/// Gets the longest timeline of all this folders children
/// </summary>
public Timeline LongestChildTimeline { get; private set; }
internal FolderEntity FolderEntity { get; set; } internal FolderEntity FolderEntity { get; set; }
internal override RenderElementEntity RenderElementEntity => FolderEntity; internal override RenderElementEntity RenderElementEntity => FolderEntity;
@ -78,6 +73,7 @@ namespace Artemis.Core
return result; return result;
} }
/// <inheritdoc />
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
if (Disposed) if (Disposed)
@ -129,6 +125,9 @@ namespace Artemis.Core
/// <returns>The newly created copy</returns> /// <returns>The newly created copy</returns>
public Folder CreateCopy() public Folder CreateCopy()
{ {
if (Parent == null)
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
FolderEntity entityCopy = JsonConvert.DeserializeObject<FolderEntity>(JsonConvert.SerializeObject(FolderEntity, settings), settings)!; FolderEntity entityCopy = JsonConvert.DeserializeObject<FolderEntity>(JsonConvert.SerializeObject(FolderEntity, settings), settings)!;
entityCopy.Id = Guid.NewGuid(); entityCopy.Id = Guid.NewGuid();
@ -139,12 +138,13 @@ namespace Artemis.Core
return new Folder(Profile, Parent, entityCopy); return new Folder(Profile, Parent, entityCopy);
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
} }
public void CalculateRenderProperties() internal void CalculateRenderProperties()
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
@ -163,57 +163,9 @@ namespace Artemis.Core
OnRenderPropertiesUpdated(); OnRenderPropertiesUpdated();
} }
protected override void Dispose(bool disposing)
{
Disposed = true;
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
Renderer.Dispose();
base.Dispose(disposing);
}
internal override void Load()
{
_expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
// Load child folders
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId;
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
FolderEntity.Order = Order;
FolderEntity.Name = Name;
FolderEntity.Enabled = Enabled;
FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
SaveRenderElement();
}
#region Rendering #region Rendering
/// <inheritdoc />
public override void Render(SKCanvas canvas) public override void Render(SKCanvas canvas)
{ {
if (Disposed) if (Disposed)
@ -278,9 +230,62 @@ namespace Artemis.Core
#endregion #endregion
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
Disposed = true;
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
Renderer.Dispose();
base.Dispose(disposing);
}
internal override void Load()
{
ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
// Load child folders
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId;
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
FolderEntity.Order = Order;
FolderEntity.Name = Name;
FolderEntity.Enabled = Enabled;
FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
SaveRenderElement();
}
#region Events #region Events
public event EventHandler RenderPropertiesUpdated; /// <summary>
/// Occurs when a property affecting the rendering properties of this folder has been updated
/// </summary>
public event EventHandler? RenderPropertiesUpdated;
private void OnRenderPropertiesUpdated() private void OnRenderPropertiesUpdated()
{ {

View File

@ -17,10 +17,10 @@ namespace Artemis.Core
public sealed class Layer : RenderProfileElement public sealed class Layer : RenderProfileElement
{ {
private LayerGeneralProperties _general; private LayerGeneralProperties _general;
private BaseLayerBrush _layerBrush; private BaseLayerBrush? _layerBrush;
private LayerShape _layerShape;
private List<ArtemisLed> _leds;
private LayerTransformProperties _transform; private LayerTransformProperties _transform;
private LayerShape? _layerShape;
private List<ArtemisLed> _leds;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided /// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided
@ -37,12 +37,12 @@ namespace Artemis.Core
Profile = Parent.Profile; Profile = Parent.Profile;
Name = name; Name = name;
Enabled = true; Enabled = true;
General = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
Transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
_layerEffects = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
_expandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
Initialize(); Initialize();
Parent.AddChild(this); Parent.AddChild(this);
@ -55,12 +55,12 @@ namespace Artemis.Core
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
General = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
Transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
_layerEffects = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
_expandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
Load(); Load();
Initialize(); Initialize();
@ -74,7 +74,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Defines the shape that is rendered by the <see cref="LayerBrush" />. /// Defines the shape that is rendered by the <see cref="LayerBrush" />.
/// </summary> /// </summary>
public LayerShape LayerShape public LayerShape? LayerShape
{ {
get => _layerShape; get => _layerShape;
set set
@ -85,24 +85,30 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets the general properties of the layer
/// </summary>
[PropertyGroupDescription(Name = "General", Description = "A collection of general properties")] [PropertyGroupDescription(Name = "General", Description = "A collection of general properties")]
public LayerGeneralProperties General public LayerGeneralProperties General
{ {
get => _general; get => _general;
set => SetAndNotify(ref _general, value); private set => SetAndNotify(ref _general, value);
} }
/// <summary>
/// Gets the transform properties of the layer
/// </summary>
[PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties")] [PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties")]
public LayerTransformProperties Transform public LayerTransformProperties Transform
{ {
get => _transform; get => _transform;
set => SetAndNotify(ref _transform, value); private set => SetAndNotify(ref _transform, value);
} }
/// <summary> /// <summary>
/// The brush that will fill the <see cref="LayerShape" />. /// The brush that will fill the <see cref="LayerShape" />.
/// </summary> /// </summary>
public BaseLayerBrush LayerBrush public BaseLayerBrush? LayerBrush
{ {
get => _layerBrush; get => _layerBrush;
internal set => SetAndNotify(ref _layerBrush, value); internal set => SetAndNotify(ref _layerBrush, value);
@ -118,12 +124,16 @@ namespace Artemis.Core
/// <returns>The newly created copy</returns> /// <returns>The newly created copy</returns>
public Layer CreateCopy() public Layer CreateCopy()
{ {
if (Parent == null)
throw new ArtemisCoreException("Cannot create a copy of a layer without a parent");
JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
LayerEntity entityCopy = JsonConvert.DeserializeObject<LayerEntity>(JsonConvert.SerializeObject(LayerEntity, settings), settings)!; LayerEntity entityCopy = JsonConvert.DeserializeObject<LayerEntity>(JsonConvert.SerializeObject(LayerEntity, settings), settings)!;
entityCopy.Id = Guid.NewGuid(); entityCopy.Id = Guid.NewGuid();
entityCopy.Name += " - Copy"; entityCopy.Name += " - Copy";
Layer copy = new Layer(Profile, Parent, entityCopy); Layer copy = new Layer(Profile, Parent, entityCopy);
if (LayerBrush?.Descriptor != null)
copy.ChangeLayerBrush(LayerBrush.Descriptor); copy.ChangeLayerBrush(LayerBrush.Descriptor);
copy.AddLeds(Leds); copy.AddLeds(Leds);
@ -162,8 +172,8 @@ namespace Artemis.Core
// Brush first in case it depends on any of the other disposables during it's own disposal // Brush first in case it depends on any of the other disposables during it's own disposal
_layerBrush?.Dispose(); _layerBrush?.Dispose();
_general?.Dispose(); _general.Dispose();
_transform?.Dispose(); _transform.Dispose();
Renderer.Dispose(); Renderer.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
@ -177,14 +187,14 @@ namespace Artemis.Core
LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved; LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved;
// Layers have two hardcoded property groups, instantiate them // Layers have two hardcoded property groups, instantiate them
Attribute? generalAttribute = Attribute.GetCustomAttribute( Attribute generalAttribute = Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(General)), GetType().GetProperty(nameof(General))!,
typeof(PropertyGroupDescriptionAttribute) typeof(PropertyGroupDescriptionAttribute)
); )!;
Attribute? transformAttribute = Attribute.GetCustomAttribute( Attribute transformAttribute = Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(Transform)), GetType().GetProperty(nameof(Transform))!,
typeof(PropertyGroupDescriptionAttribute) typeof(PropertyGroupDescriptionAttribute)
); )!;
General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute; General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute;
General.Initialize(this, "General.", Constants.CorePluginFeature); General.Initialize(this, "General.", Constants.CorePluginFeature);
Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute; Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute;
@ -204,7 +214,7 @@ namespace Artemis.Core
Enabled = LayerEntity.Enabled; Enabled = LayerEntity.Enabled;
Order = LayerEntity.Order; Order = LayerEntity.Order;
_expandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
LoadRenderElement(); LoadRenderElement();
} }
@ -221,11 +231,11 @@ namespace Artemis.Core
LayerEntity.Name = Name; LayerEntity.Name = Name;
LayerEntity.ProfileId = Profile.EntityId; LayerEntity.ProfileId = Profile.EntityId;
LayerEntity.ExpandedPropertyGroups.Clear(); LayerEntity.ExpandedPropertyGroups.Clear();
LayerEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups); LayerEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
General.ApplyToEntity(); General.ApplyToEntity();
Transform.ApplyToEntity(); Transform.ApplyToEntity();
LayerBrush?.BaseProperties.ApplyToEntity(); LayerBrush?.BaseProperties?.ApplyToEntity();
// LEDs // LEDs
LayerEntity.Leds.Clear(); LayerEntity.Leds.Clear();
@ -246,7 +256,7 @@ namespace Artemis.Core
#region Shape management #region Shape management
private void ShapeTypeOnCurrentValueSet(object sender, EventArgs e) private void ShapeTypeOnCurrentValueSet(object? sender, EventArgs e)
{ {
ApplyShapeType(); ApplyShapeType();
} }
@ -316,8 +326,11 @@ namespace Artemis.Core
{ {
General.Update(timeline); General.Update(timeline);
Transform.Update(timeline); Transform.Update(timeline);
if (LayerBrush != null)
{
LayerBrush.BaseProperties?.Update(timeline); LayerBrush.BaseProperties?.Update(timeline);
LayerBrush.Update(timeline.Delta.TotalSeconds); LayerBrush.Update(timeline.Delta.TotalSeconds);
}
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{ {
@ -328,6 +341,9 @@ namespace Artemis.Core
private void RenderTimeline(Timeline timeline, SKCanvas canvas) private void RenderTimeline(Timeline timeline, SKCanvas canvas)
{ {
if (Path == null || LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering");
if (timeline.IsFinished) if (timeline.IsFinished)
return; return;
@ -392,6 +408,11 @@ namespace Artemis.Core
private void DelegateRendering(SKRect bounds) private void DelegateRendering(SKRect bounds)
{ {
if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering");
if (Renderer.Canvas == null || Renderer.Paint == null)
throw new ArtemisCoreException("Failed to open layer render context");
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PreProcess(Renderer.Canvas, bounds, Renderer.Paint); baseLayerEffect.PreProcess(Renderer.Canvas, bounds, Renderer.Paint);
@ -459,12 +480,18 @@ namespace Artemis.Core
/// If true, treats the layer as if it is located at 0,0 instead of its actual position on the /// If true, treats the layer as if it is located at 0,0 instead of its actual position on the
/// surface /// surface
/// </param> /// </param>
/// <param name="includeTranslation">Whether translation should be included</param>
/// <param name="includeScale">Whether the scale should be included</param>
/// <param name="includeRotation">Whether the rotation should be included</param>
/// <returns>The transformation matrix containing the current transformation settings</returns> /// <returns>The transformation matrix containing the current transformation settings</returns>
public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation) public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (Path == null)
return SKMatrix.Empty;
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
float rotationProperty = Transform.Rotation.CurrentValue; float rotationProperty = Transform.Rotation.CurrentValue;
@ -478,25 +505,23 @@ namespace Artemis.Core
SKMatrix transform = SKMatrix.Empty; SKMatrix transform = SKMatrix.Empty;
if (includeTranslation) if (includeTranslation)
{
// transform is always SKMatrix.Empty here... // transform is always SKMatrix.Empty here...
transform = SKMatrix.MakeTranslation(x, y); transform = SKMatrix.CreateTranslation(x, y);
}
if (includeScale) if (includeScale)
{ {
if (transform == SKMatrix.Empty) if (transform == SKMatrix.Empty)
transform = SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); transform = SKMatrix.CreateScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y);
else else
transform = transform.PostConcat(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y)); transform = transform.PostConcat(SKMatrix.CreateScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y));
} }
if (includeRotation) if (includeRotation)
{ {
if (transform == SKMatrix.Empty) if (transform == SKMatrix.Empty)
transform = SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y); transform = SKMatrix.CreateRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y);
else else
transform = transform.PostConcat(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); transform = transform.PostConcat(SKMatrix.CreateRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
} }
return transform; return transform;
@ -568,7 +593,7 @@ namespace Artemis.Core
List<ArtemisLed> availableLeds = surface.Devices.SelectMany(d => d.Leds).ToList(); List<ArtemisLed> availableLeds = surface.Devices.SelectMany(d => d.Leds).ToList();
foreach (LedEntity ledEntity in LayerEntity.Leds) foreach (LedEntity ledEntity in LayerEntity.Leds)
{ {
ArtemisLed match = availableLeds.FirstOrDefault(a => a.Device.RgbDevice.GetDeviceIdentifier() == ledEntity.DeviceIdentifier && ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.RgbDevice.GetDeviceIdentifier() == ledEntity.DeviceIdentifier &&
a.RgbLed.Id.ToString() == ledEntity.LedName); a.RgbLed.Id.ToString() == ledEntity.LedName);
if (match != null) if (match != null)
leds.Add(match); leds.Add(match);
@ -598,7 +623,7 @@ namespace Artemis.Core
} }
// Ensure the brush reference matches the brush // Ensure the brush reference matches the brush
LayerBrushReference current = General.BrushReference.BaseValue; LayerBrushReference? current = General.BrushReference.BaseValue;
if (!descriptor.MatchesLayerBrushReference(current)) if (!descriptor.MatchesLayerBrushReference(current))
General.BrushReference.BaseValue = new LayerBrushReference(descriptor); General.BrushReference.BaseValue = new LayerBrushReference(descriptor);
@ -620,11 +645,13 @@ namespace Artemis.Core
internal void ActivateLayerBrush() internal void ActivateLayerBrush()
{ {
LayerBrushReference current = General.BrushReference.CurrentValue; LayerBrushReference? current = General.BrushReference.CurrentValue;
if (current == null) if (current == null)
return; return;
LayerBrushDescriptor? descriptor = LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor; LayerBrushDescriptor? descriptor = current.LayerBrushProviderId != null && current.BrushType != null
? LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor
: null;
descriptor?.CreateInstance(this); descriptor?.CreateInstance(this);
OnLayerBrushUpdated(); OnLayerBrushUpdated();
@ -646,19 +673,19 @@ namespace Artemis.Core
#region Event handlers #region Event handlers
private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e)
{ {
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor) if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
DeactivateLayerBrush(); DeactivateLayerBrush();
} }
private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e) private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e)
{ {
if (LayerBrush != null || General.BrushReference?.CurrentValue == null) if (LayerBrush != null || !General.PropertiesInitialized)
return; return;
LayerBrushReference current = General.BrushReference.CurrentValue; LayerBrushReference? current = General.BrushReference.CurrentValue;
if (e.Registration.PluginFeature.Id == current.LayerBrushProviderId && if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId &&
e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType) e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
ActivateLayerBrush(); ActivateLayerBrush();
} }
@ -667,8 +694,15 @@ namespace Artemis.Core
#region Events #region Events
public event EventHandler RenderPropertiesUpdated; /// <summary>
public event EventHandler LayerBrushUpdated; /// Occurs when a property affecting the rendering properties of this layer has been updated
/// </summary>
public event EventHandler? RenderPropertiesUpdated;
/// <summary>
/// Occurs when the layer brush of this layer has been updated
/// </summary>
public event EventHandler? LayerBrushUpdated;
private void OnRenderPropertiesUpdated() private void OnRenderPropertiesUpdated()
{ {
@ -683,15 +717,35 @@ namespace Artemis.Core
#endregion #endregion
} }
/// <summary>
/// Represents a type of layer shape
/// </summary>
public enum LayerShapeType public enum LayerShapeType
{ {
/// <summary>
/// A circular layer shape
/// </summary>
Ellipse, Ellipse,
/// <summary>
/// A rectangular layer shape
/// </summary>
Rectangle Rectangle
} }
/// <summary>
/// Represents a layer transform mode
/// </summary>
public enum LayerTransformMode public enum LayerTransformMode
{ {
/// <summary>
/// Normal transformation
/// </summary>
Normal, Normal,
/// <summary>
/// Transforms only a clip
/// </summary>
Clip Clip
} }
} }

View File

@ -7,10 +7,17 @@ namespace Artemis.Core
/// </summary> /// </summary>
public class LayerBrushReference public class LayerBrushReference
{ {
/// <summary>
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
/// </summary>
public LayerBrushReference() public LayerBrushReference()
{ {
} }
/// <summary>
/// Creates a new instance of the <see cref="LayerBrushReference" /> class
/// </summary>
/// <param name="descriptor">The descriptor to point the new reference at</param>
public LayerBrushReference(LayerBrushDescriptor descriptor) public LayerBrushReference(LayerBrushDescriptor descriptor)
{ {
LayerBrushProviderId = descriptor.Provider.Id; LayerBrushProviderId = descriptor.Provider.Id;
@ -20,11 +27,11 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// The ID of the layer brush provided the brush was provided by /// The ID of the layer brush provided the brush was provided by
/// </summary> /// </summary>
public string LayerBrushProviderId { get; set; } public string? LayerBrushProviderId { get; set; }
/// <summary> /// <summary>
/// The full type name of the brush descriptor /// The full type name of the brush descriptor
/// </summary> /// </summary>
public string BrushType { get; set; } public string? BrushType { get; set; }
} }
} }

View File

@ -1,31 +1,51 @@
using SkiaSharp; using SkiaSharp;
#pragma warning disable 8618
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents the general properties of a layer
/// </summary>
public class LayerGeneralProperties : LayerPropertyGroup public class LayerGeneralProperties : LayerPropertyGroup
{ {
/// <summary>
/// The type of shape to draw in this layer
/// </summary>
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")] [PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; } public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
/// <summary>
/// How to blend this layer into the resulting image
/// </summary>
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")] [PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; } public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
/// <summary>
/// How the transformation properties are applied to the layer
/// </summary>
[PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")] [PropertyDescription(Name = "Transform mode", Description = "How the transformation properties are applied to the layer")]
public EnumLayerProperty<LayerTransformMode> TransformMode { get; set; } public EnumLayerProperty<LayerTransformMode> TransformMode { get; set; }
/// <summary>
/// The type of brush to use for this layer
/// </summary>
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")] [PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
public LayerBrushReferenceLayerProperty BrushReference { get; set; } public LayerBrushReferenceLayerProperty BrushReference { get; set; }
/// <inheritdoc />
protected override void PopulateDefaults() protected override void PopulateDefaults()
{ {
ShapeType.DefaultValue = LayerShapeType.Rectangle; ShapeType.DefaultValue = LayerShapeType.Rectangle;
BlendMode.DefaultValue = SKBlendMode.SrcOver; BlendMode.DefaultValue = SKBlendMode.SrcOver;
} }
/// <inheritdoc />
protected override void EnableProperties() protected override void EnableProperties()
{ {
} }
/// <inheritdoc />
protected override void DisableProperties() protected override void DisableProperties()
{ {
} }

View File

@ -2,27 +2,30 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a description attribute used to decorate layer properties
/// </summary>
public class PropertyDescriptionAttribute : Attribute public class PropertyDescriptionAttribute : Attribute
{ {
/// <summary> /// <summary>
/// The user-friendly name for this property, shown in the UI /// The user-friendly name for this property, shown in the UI
/// </summary> /// </summary>
public string Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// The user-friendly description for this property, shown in the UI /// The user-friendly description for this property, shown in the UI
/// </summary> /// </summary>
public string Description { get; set; } public string? Description { get; set; }
/// <summary> /// <summary>
/// Input prefix to show before input elements in the UI /// Input prefix to show before input elements in the UI
/// </summary> /// </summary>
public string InputPrefix { get; set; } public string? InputPrefix { get; set; }
/// <summary> /// <summary>
/// Input affix to show behind input elements in the UI /// Input affix to show behind input elements in the UI
/// </summary> /// </summary>
public string InputAffix { get; set; } public string? InputAffix { get; set; }
/// <summary> /// <summary>
/// The input drag step size, used in the UI /// The input drag step size, used in the UI
@ -32,12 +35,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Minimum input value, only enforced in the UI /// Minimum input value, only enforced in the UI
/// </summary> /// </summary>
public object MinInputValue { get; set; } public object? MinInputValue { get; set; }
/// <summary> /// <summary>
/// Maximum input value, only enforced in the UI /// Maximum input value, only enforced in the UI
/// </summary> /// </summary>
public object MaxInputValue { get; set; } public object? MaxInputValue { get; set; }
/// <summary> /// <summary>
/// Whether or not keyframes are always disabled /// Whether or not keyframes are always disabled

View File

@ -2,16 +2,19 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a description attribute used to decorate layer property groups
/// </summary>
public class PropertyGroupDescriptionAttribute : Attribute public class PropertyGroupDescriptionAttribute : Attribute
{ {
/// <summary> /// <summary>
/// The user-friendly name for this property, shown in the UI. /// The user-friendly name for this property, shown in the UI.
/// </summary> /// </summary>
public string Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// The user-friendly description for this property, shown in the UI. /// The user-friendly description for this property, shown in the UI.
/// </summary> /// </summary>
public string Description { get; set; } public string? Description { get; set; }
} }
} }

View File

@ -25,9 +25,22 @@ namespace Artemis.Core
/// </summary> /// </summary>
protected LayerProperty() protected LayerProperty()
{ {
// Cant define generic types as nullable ¯\_(ツ)_/¯
CurrentValue = default!;
DefaultValue = default!;
_baseValue = default!;
_keyframes = new List<LayerPropertyKeyframe<T>>(); _keyframes = new List<LayerPropertyKeyframe<T>>();
} }
/// <summary>
/// Returns the type of the property
/// </summary>
public Type GetPropertyType()
{
return typeof(T);
}
/// <inheritdoc /> /// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; } public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
@ -57,14 +70,6 @@ namespace Artemis.Core
dataBinding.Dispose(); dataBinding.Dispose();
} }
/// <summary>
/// Returns the type of the property
/// </summary>
public Type GetPropertyType()
{
return typeof(T);
}
#region Hierarchy #region Hierarchy
private bool _isHidden; private bool _isHidden;
@ -150,11 +155,13 @@ namespace Artemis.Core
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
if (time == null || !KeyframesEnabled || !KeyframesSupported) if (time == null || !KeyframesEnabled || !KeyframesSupported)
{
BaseValue = value; BaseValue = value;
}
else else
{ {
// If on a keyframe, update the keyframe // If on a keyframe, update the keyframe
LayerPropertyKeyframe<T> currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value); LayerPropertyKeyframe<T>? currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found // Create a new keyframe if none found
if (currentKeyframe == null) if (currentKeyframe == null)
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this)); AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
@ -222,12 +229,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the current keyframe in the timeline according to the current progress /// Gets the current keyframe in the timeline according to the current progress
/// </summary> /// </summary>
public LayerPropertyKeyframe<T> CurrentKeyframe { get; protected set; } public LayerPropertyKeyframe<T>? CurrentKeyframe { get; protected set; }
/// <summary> /// <summary>
/// Gets the next keyframe in the timeline according to the current progress /// Gets the next keyframe in the timeline according to the current progress
/// </summary> /// </summary>
public LayerPropertyKeyframe<T> NextKeyframe { get; protected set; } public LayerPropertyKeyframe<T>? NextKeyframe { get; protected set; }
/// <summary> /// <summary>
/// Adds a keyframe to the layer property /// Adds a keyframe to the layer property
@ -249,26 +256,6 @@ namespace Artemis.Core
OnKeyframeAdded(); OnKeyframeAdded();
} }
/// <summary>
/// Removes a keyframe from the layer property
/// </summary>
/// <param name="keyframe">The keyframe to remove</param>
public LayerPropertyKeyframe<T> CopyKeyframe(LayerPropertyKeyframe<T> keyframe)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
LayerPropertyKeyframe<T> newKeyframe = new LayerPropertyKeyframe<T>(
keyframe.Value,
keyframe.Position,
keyframe.EasingFunction,
keyframe.LayerProperty
);
AddKeyframe(newKeyframe);
return newKeyframe;
}
/// <summary> /// <summary>
/// Removes a keyframe from the layer property /// Removes a keyframe from the layer property
/// </summary> /// </summary>
@ -282,7 +269,6 @@ namespace Artemis.Core
return; return;
_keyframes.Remove(keyframe); _keyframes.Remove(keyframe);
keyframe.LayerProperty = null;
SortKeyframes(); SortKeyframes();
OnKeyframeRemoved(); OnKeyframeRemoved();
} }
@ -303,14 +289,25 @@ namespace Artemis.Core
// The current keyframe is the last keyframe before the current time // The current keyframe is the last keyframe before the current time
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= timeline.Position); CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= timeline.Position);
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current // Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current
if (CurrentKeyframe != null)
{
int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1; int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
}
else
{
NextKeyframe = null;
}
// No need to update the current value if either of the keyframes are null // No need to update the current value if either of the keyframes are null
if (CurrentKeyframe == null) if (CurrentKeyframe == null)
{
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue; CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue;
}
else if (NextKeyframe == null) else if (NextKeyframe == null)
{
CurrentValue = CurrentKeyframe.Value; CurrentValue = CurrentKeyframe.Value;
}
// Only determine progress and current value if both keyframes are present // Only determine progress and current value if both keyframes are present
else else
{ {
@ -325,8 +322,11 @@ namespace Artemis.Core
#region Data bindings #region Data bindings
// ReSharper disable InconsistentNaming
internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new List<IDataBindingRegistration>(); internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new List<IDataBindingRegistration>();
internal readonly List<IDataBinding> _dataBindings = new List<IDataBinding>(); internal readonly List<IDataBinding> _dataBindings = new List<IDataBinding>();
// ReSharper restore InconsistentNaming
/// <summary> /// <summary>
/// Gets whether data bindings are supported on this type of property /// Gets whether data bindings are supported on this type of property
@ -342,27 +342,36 @@ namespace Artemis.Core
/// Gets a data binding registration by the expression used to register it /// Gets a data binding registration by the expression used to register it
/// <para>Note: The expression must exactly match the one used to register the data binding</para> /// <para>Note: The expression must exactly match the one used to register the data binding</para>
/// </summary> /// </summary>
public DataBindingRegistration<T, TProperty> GetDataBindingRegistration<TProperty>(Expression<Func<T, TProperty>> propertyExpression) public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
{ {
return GetDataBindingRegistration<TProperty>(propertyExpression.ToString()); return GetDataBindingRegistration<TProperty>(propertyExpression.ToString());
} }
public DataBindingRegistration<T, TProperty> GetDataBindingRegistration<TProperty>(string expression) internal DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string expression)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
IDataBindingRegistration match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration && IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.PropertyExpression.ToString() == expression); registration.PropertyExpression.ToString() == expression);
return (DataBindingRegistration<T, TProperty>?) match;
return (DataBindingRegistration<T, TProperty>) match;
} }
/// <summary>
/// Gets a list containing all data binding registrations of this layer property
/// </summary>
/// <returns>A list containing all data binding registrations of this layer property</returns>
public List<IDataBindingRegistration> GetAllDataBindingRegistrations() public List<IDataBindingRegistration> GetAllDataBindingRegistrations()
{ {
return _dataBindingRegistrations; return _dataBindingRegistrations;
} }
/// <summary>
/// Registers a data binding property so that is available to the data binding system
/// </summary>
/// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="propertyExpression">The expression pointing to the value to register</param>
/// <param name="converter">The converter to use while applying the data binding</param>
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter) public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter)
{ {
if (_disposed) if (_disposed)
@ -372,10 +381,8 @@ namespace Artemis.Core
throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'"); throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'");
if (converter.SupportedType != propertyExpression.ReturnType) if (converter.SupportedType != propertyExpression.ReturnType)
{
throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " + throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " +
"because the provided converter does not support the property's type"); "because the provided converter does not support the property's type");
}
_dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyExpression)); _dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyExpression));
} }
@ -412,6 +419,7 @@ namespace Artemis.Core
_dataBindings.Remove(dataBinding); _dataBindings.Remove(dataBinding);
if (dataBinding.Registration != null)
dataBinding.Registration.DataBinding = null; dataBinding.Registration.DataBinding = null;
dataBinding.Dispose(); dataBinding.Dispose();
OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty)); OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty));
@ -471,17 +479,15 @@ namespace Artemis.Core
if (!IsLoadedFromStorage) if (!IsLoadedFromStorage)
ApplyDefaultValue(); ApplyDefaultValue();
else else
{
try try
{ {
if (Entity.Value != null) if (Entity.Value != null)
BaseValue = JsonConvert.DeserializeObject<T>(Entity.Value); BaseValue = JsonConvert.DeserializeObject<T>(Entity.Value);
} }
catch (JsonException e) catch (JsonException)
{ {
// ignored for now // ignored for now
} }
}
CurrentValue = BaseValue; CurrentValue = BaseValue;
KeyframesEnabled = Entity.KeyframesEnabled; KeyframesEnabled = Entity.KeyframesEnabled;
@ -494,7 +500,7 @@ namespace Artemis.Core
.Select(k => new LayerPropertyKeyframe<T>(JsonConvert.DeserializeObject<T>(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this)) .Select(k => new LayerPropertyKeyframe<T>(JsonConvert.DeserializeObject<T>(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this))
); );
} }
catch (JsonException e) catch (JsonException)
{ {
// ignored for now // ignored for now
} }
@ -502,15 +508,14 @@ namespace Artemis.Core
_dataBindings.Clear(); _dataBindings.Clear();
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations) foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
{ {
IDataBinding dataBinding = dataBindingRegistration.CreateDataBinding(); IDataBinding? dataBinding = dataBindingRegistration.CreateDataBinding();
if (dataBinding != null) if (dataBinding != null)
_dataBindings.Add(dataBinding); _dataBindings.Add(dataBinding);
} }
} }
/// <summary> /// <summary>
/// Saves the property to the underlying property entity that was configured when calling /// Saves the property to the underlying property entity
/// <see cref="ApplyToLayerProperty" />
/// </summary> /// </summary>
public void Save() public void Save()
{ {
@ -542,79 +547,103 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs once every frame when the layer property is updated /// Occurs once every frame when the layer property is updated
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> Updated; public event EventHandler<LayerPropertyEventArgs<T>>? Updated;
/// <summary> /// <summary>
/// Occurs when the current value of the layer property was updated by some form of input /// Occurs when the current value of the layer property was updated by some form of input
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> CurrentValueSet; public event EventHandler<LayerPropertyEventArgs<T>>? CurrentValueSet;
/// <summary> /// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated /// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> VisibilityChanged; public event EventHandler<LayerPropertyEventArgs<T>>? VisibilityChanged;
/// <summary> /// <summary>
/// Occurs when keyframes are enabled/disabled /// Occurs when keyframes are enabled/disabled
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframesToggled; public event EventHandler<LayerPropertyEventArgs<T>>? KeyframesToggled;
/// <summary> /// <summary>
/// Occurs when a new keyframe was added to the layer property /// Occurs when a new keyframe was added to the layer property
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeAdded; public event EventHandler<LayerPropertyEventArgs<T>>? KeyframeAdded;
/// <summary> /// <summary>
/// Occurs when a keyframe was removed from the layer property /// Occurs when a keyframe was removed from the layer property
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeRemoved; public event EventHandler<LayerPropertyEventArgs<T>>? KeyframeRemoved;
/// <summary> /// <summary>
/// Occurs when a data binding has been enabled /// Occurs when a data binding has been enabled
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> DataBindingEnabled; public event EventHandler<LayerPropertyEventArgs<T>>? DataBindingEnabled;
/// <summary> /// <summary>
/// Occurs when a data binding has been disabled /// Occurs when a data binding has been disabled
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> DataBindingDisabled; public event EventHandler<LayerPropertyEventArgs<T>>? DataBindingDisabled;
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated() protected virtual void OnUpdated()
{ {
Updated?.Invoke(this, new LayerPropertyEventArgs<T>(this)); Updated?.Invoke(this, new LayerPropertyEventArgs<T>(this));
} }
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet() protected virtual void OnCurrentValueSet()
{ {
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs<T>(this)); CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs<T>(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
} }
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged() protected virtual void OnVisibilityChanged()
{ {
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this)); VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this));
} }
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled() protected virtual void OnKeyframesToggled()
{ {
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs<T>(this)); KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs<T>(this));
} }
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded() protected virtual void OnKeyframeAdded()
{ {
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs<T>(this)); KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs<T>(this));
} }
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved() protected virtual void OnKeyframeRemoved()
{ {
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs<T>(this)); KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs<T>(this));
} }
/// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary>
protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs<T> e) protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs<T> e)
{ {
DataBindingEnabled?.Invoke(this, e); DataBindingEnabled?.Invoke(this, e);
} }
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs<T> e) protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs<T> e)
{ {
DataBindingDisabled?.Invoke(this, e); DataBindingDisabled?.Invoke(this, e);

View File

@ -21,9 +21,9 @@ namespace Artemis.Core
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty)
{ {
_position = position; _position = position;
_layerProperty = layerProperty;
_value = value;
Value = value;
LayerProperty = layerProperty;
EasingFunction = easingFunction; EasingFunction = easingFunction;
} }

View File

@ -11,6 +11,13 @@ using Humanizer;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a property group on a layer
/// <para>
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
/// initialize these for you.
/// </para>
/// </summary>
public abstract class LayerPropertyGroup : IDisposable public abstract class LayerPropertyGroup : IDisposable
{ {
private readonly List<ILayerProperty> _layerProperties; private readonly List<ILayerProperty> _layerProperties;
@ -18,6 +25,9 @@ namespace Artemis.Core
private bool _disposed; private bool _disposed;
private bool _isHidden; private bool _isHidden;
/// <summary>
/// A base constructor for a <see cref="LayerPropertyGroup" />
/// </summary>
protected LayerPropertyGroup() protected LayerPropertyGroup()
{ {
_layerProperties = new List<ILayerProperty>(); _layerProperties = new List<ILayerProperty>();
@ -87,22 +97,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups => _layerPropertyGroups.AsReadOnly();
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DisableProperties();
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
#endregion
/// <summary> /// <summary>
/// Recursively gets all layer properties on this group and any subgroups /// Recursively gets all layer properties on this group and any subgroups
/// </summary> /// </summary>
@ -165,7 +159,9 @@ namespace Artemis.Core
{ {
Attribute? propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); Attribute? propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
if (propertyDescription != null) if (propertyDescription != null)
{
InitializeProperty(propertyInfo, (PropertyDescriptionAttribute) propertyDescription); InitializeProperty(propertyInfo, (PropertyDescriptionAttribute) propertyDescription);
}
else else
{ {
Attribute? propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); Attribute? propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
@ -265,6 +261,22 @@ namespace Artemis.Core
return entity; return entity;
} }
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DisableProperties();
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
#endregion
#region Events #region Events
/// <summary> /// <summary>

View File

@ -1,34 +1,57 @@
using SkiaSharp; using SkiaSharp;
#pragma warning disable 8618
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents the transform properties of a layer
/// </summary>
public class LayerTransformProperties : LayerPropertyGroup public class LayerTransformProperties : LayerPropertyGroup
{ {
/// <summary>
/// The point at which the shape is attached to its position
/// </summary>
[PropertyDescription(Description = "The point at which the shape is attached to its position", InputStepSize = 0.001f)] [PropertyDescription(Description = "The point at which the shape is attached to its position", InputStepSize = 0.001f)]
public SKPointLayerProperty AnchorPoint { get; set; } public SKPointLayerProperty AnchorPoint { get; set; }
/// <summary>
/// The position of the shape
/// </summary>
[PropertyDescription(Description = "The position of the shape", InputStepSize = 0.001f)] [PropertyDescription(Description = "The position of the shape", InputStepSize = 0.001f)]
public SKPointLayerProperty Position { get; set; } public SKPointLayerProperty Position { get; set; }
/// <summary>
/// The scale of the shape
/// </summary>
[PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)] [PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)]
public SKSizeLayerProperty Scale { get; set; } public SKSizeLayerProperty Scale { get; set; }
/// <summary>
/// The rotation of the shape in degree
/// </summary>
[PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°")] [PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°")]
public FloatLayerProperty Rotation { get; set; } public FloatLayerProperty Rotation { get; set; }
/// <summary>
/// The opacity of the shape
/// </summary>
[PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)] [PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)]
public FloatLayerProperty Opacity { get; set; } public FloatLayerProperty Opacity { get; set; }
/// <inheritdoc />
protected override void PopulateDefaults() protected override void PopulateDefaults()
{ {
Scale.DefaultValue = new SKSize(100, 100); Scale.DefaultValue = new SKSize(100, 100);
Opacity.DefaultValue = 100; Opacity.DefaultValue = 100;
} }
/// <inheritdoc />
protected override void EnableProperties() protected override void EnableProperties()
{ {
} }
/// <inheritdoc />
protected override void DisableProperties() protected override void DisableProperties()
{ {
} }

View File

@ -7,6 +7,9 @@ using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a profile containing folders and layers
/// </summary>
public sealed class Profile : ProfileElement public sealed class Profile : ProfileElement
{ {
private bool _isActivated; private bool _isActivated;
@ -39,8 +42,14 @@ namespace Artemis.Core
Load(); Load();
} }
/// <summary>
/// Gets the module backing this profile
/// </summary>
public ProfileModule Module { get; } public ProfileModule Module { get; }
/// <summary>
/// Gets a boolean indicating whether this profile is activated
/// </summary>
public bool IsActivated public bool IsActivated
{ {
get => _isActivated; get => _isActivated;
@ -52,6 +61,7 @@ namespace Artemis.Core
internal Stack<string> UndoStack { get; set; } internal Stack<string> UndoStack { get; set; }
internal Stack<string> RedoStack { get; set; } internal Stack<string> RedoStack { get; set; }
/// <inheritdoc />
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
lock (this) lock (this)
@ -66,6 +76,7 @@ namespace Artemis.Core
} }
} }
/// <inheritdoc />
public override void Render(SKCanvas canvas) public override void Render(SKCanvas canvas)
{ {
lock (this) lock (this)
@ -87,6 +98,11 @@ namespace Artemis.Core
child.Reset(); child.Reset();
} }
/// <summary>
/// Retrieves the root folder of this profile
/// </summary>
/// <returns>The root folder of the profile</returns>
/// <exception cref="ObjectDisposedException"></exception>
public Folder GetRootFolder() public Folder GetRootFolder()
{ {
if (Disposed) if (Disposed)
@ -95,6 +111,28 @@ namespace Artemis.Core
return (Folder) Children.Single(); return (Folder) Children.Single();
} }
/// <inheritdoc />
public override string ToString()
{
return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}";
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
OnDeactivating();
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
ChildrenList.Clear();
IsActivated = false;
Disposed = true;
}
internal override void Load() internal override void Load()
{ {
if (Disposed) if (Disposed)
@ -116,28 +154,10 @@ namespace Artemis.Core
Folder _ = new Folder(this, "Root folder"); Folder _ = new Folder(this, "Root folder");
} }
else else
{
AddChild(new Folder(this, this, rootFolder)); AddChild(new Folder(this, this, rootFolder));
} }
} }
public override string ToString()
{
return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}";
}
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
OnDeactivating();
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
ChildrenList.Clear();
IsActivated = false;
Disposed = true;
} }
internal override void Save() internal override void Save()
@ -189,12 +209,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs when the profile has been activated. /// Occurs when the profile has been activated.
/// </summary> /// </summary>
public event EventHandler Activated; public event EventHandler? Activated;
/// <summary> /// <summary>
/// Occurs when the profile is being deactivated. /// Occurs when the profile is being deactivated.
/// </summary> /// </summary>
public event EventHandler Deactivated; public event EventHandler? Deactivated;
private void OnActivated() private void OnActivated()
{ {

View File

@ -4,6 +4,9 @@ using Artemis.Storage.Entities.Profile;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a descriptor that describes a profile
/// </summary>
public class ProfileDescriptor public class ProfileDescriptor
{ {
internal ProfileDescriptor(ProfileModule profileModule, ProfileEntity profileEntity) internal ProfileDescriptor(ProfileModule profileModule, ProfileEntity profileEntity)
@ -15,10 +18,24 @@ namespace Artemis.Core
IsLastActiveProfile = profileEntity.IsActive; IsLastActiveProfile = profileEntity.IsActive;
} }
public bool IsLastActiveProfile { get; set; } /// <summary>
/// Gets a boolean indicating whether this was the last active profile
/// </summary>
public bool IsLastActiveProfile { get; }
/// <summary>
/// Gets the unique ID of the profile by which it can be loaded from storage
/// </summary>
public Guid Id { get; } public Guid Id { get; }
/// <summary>
/// Gets the module backing the profile
/// </summary>
public ProfileModule ProfileModule { get; } public ProfileModule ProfileModule { get; }
/// <summary>
/// Gets the name of the profile
/// </summary>
public string Name { get; } public string Name { get; }
} }
} }

View File

@ -6,18 +6,22 @@ using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents an element of a <see cref="Profile" />
/// </summary>
public abstract class ProfileElement : CorePropertyChanged, IDisposable public abstract class ProfileElement : CorePropertyChanged, IDisposable
{ {
private bool _enabled; private bool _enabled;
private Guid _entityId; private Guid _entityId;
private string _name; private string _name;
private int _order; private int _order;
private ProfileElement _parent; private ProfileElement? _parent;
private Profile _profile; private Profile _profile;
protected List<ProfileElement> ChildrenList;
protected bool Disposed;
protected ProfileElement() internal List<ProfileElement> ChildrenList;
internal bool Disposed;
internal ProfileElement()
{ {
ChildrenList = new List<ProfileElement>(); ChildrenList = new List<ProfileElement>();
} }
@ -43,7 +47,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the parent of this element /// Gets the parent of this element
/// </summary> /// </summary>
public ProfileElement Parent public ProfileElement? Parent
{ {
get => _parent; get => _parent;
internal set => SetAndNotify(ref _parent, value); internal set => SetAndNotify(ref _parent, value);
@ -233,6 +237,9 @@ namespace Artemis.Core
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Disposes the profile element
/// </summary>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
@ -244,14 +251,27 @@ namespace Artemis.Core
#region Events #region Events
public event EventHandler ChildAdded; /// <summary>
public event EventHandler ChildRemoved; /// Occurs when a child was added to the <see cref="Children" /> list
/// </summary>
public event EventHandler? ChildAdded;
/// <summary>
/// Occurs when a child was removed from the <see cref="Children" /> list
/// </summary>
public event EventHandler? ChildRemoved;
/// <summary>
/// Invokes the <see cref="ChildAdded" /> event
/// </summary>
protected virtual void OnChildAdded() protected virtual void OnChildAdded()
{ {
ChildAdded?.Invoke(this, EventArgs.Empty); ChildAdded?.Invoke(this, EventArgs.Empty);
} }
/// <summary>
/// Invokes the <see cref="ChildRemoved" /> event
/// </summary>
protected virtual void OnChildRemoved() protected virtual void OnChildRemoved()
{ {
ChildRemoved?.Invoke(this, EventArgs.Empty); ChildRemoved?.Invoke(this, EventArgs.Empty);

View File

@ -1,6 +0,0 @@
namespace Artemis.Core
{
public abstract class PropertiesProfileElement : ProfileElement
{
}
}

View File

@ -11,9 +11,12 @@ using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents an element of a <see cref="Profile" /> that has advanced rendering capabilities
/// </summary>
public abstract class RenderProfileElement : ProfileElement public abstract class RenderProfileElement : ProfileElement
{ {
protected RenderProfileElement() internal RenderProfileElement()
{ {
Timeline = new Timeline(); Timeline = new Timeline();
Renderer = new Renderer(); Renderer = new Renderer();
@ -22,8 +25,28 @@ namespace Artemis.Core
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
} }
/// <summary>
/// Creates a list of all layer properties present on this render element
/// </summary>
/// <returns>A list of all layer properties present on this render element</returns>
public abstract List<ILayerProperty> GetAllLayerProperties(); public abstract List<ILayerProperty> GetAllLayerProperties();
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
internal void LoadRenderElement() internal void LoadRenderElement()
{ {
DisplayCondition = RenderElementEntity.DisplayCondition != null DisplayCondition = RenderElementEntity.DisplayCondition != null
@ -73,7 +96,8 @@ namespace Artemis.Core
public Timeline Timeline { get; private set; } public Timeline Timeline { get; private set; }
/// <summary> /// <summary>
/// Updates the <see cref="Timeline"/> according to the provided <paramref name="deltaTime"/> and current display condition status /// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
/// condition status
/// </summary> /// </summary>
public void UpdateTimeline(double deltaTime) public void UpdateTimeline(double deltaTime)
{ {
@ -86,31 +110,16 @@ namespace Artemis.Core
#endregion #endregion
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
#region Properties #region Properties
private ProfileElement _parent; private ProfileElement? _parent;
private SKPath _path; private SKPath? _path;
internal abstract RenderElementEntity RenderElementEntity { get; } internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary> /// <summary>
/// Gets the parent of this element /// Gets the parent of this element
/// </summary> /// </summary>
public new ProfileElement Parent public new ProfileElement? Parent
{ {
get => _parent; get => _parent;
internal set internal set
@ -124,7 +133,7 @@ namespace Artemis.Core
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped. /// clipped.
/// </summary> /// </summary>
public SKPath Path public SKPath? Path
{ {
get => _path; get => _path;
protected set protected set
@ -150,20 +159,30 @@ namespace Artemis.Core
#region Property group expansion #region Property group expansion
protected List<string> _expandedPropertyGroups; internal List<string> ExpandedPropertyGroups;
private SKRect _bounds; private SKRect _bounds;
/// <summary>
/// Determines whether the provided property group is expanded
/// </summary>
/// <param name="layerPropertyGroup">The property group to check</param>
/// <returns>A boolean indicating whether the provided property group is expanded</returns>
public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup) public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup)
{ {
return _expandedPropertyGroups.Contains(layerPropertyGroup.Path); return ExpandedPropertyGroups.Contains(layerPropertyGroup.Path);
} }
/// <summary>
/// Expands or collapses the provided property group
/// </summary>
/// <param name="layerPropertyGroup">The group to expand or collapse</param>
/// <param name="expanded">Whether to expand or collapse the property group</param>
public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded) public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded)
{ {
if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup)) if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup))
_expandedPropertyGroups.Remove(layerPropertyGroup.Path); ExpandedPropertyGroups.Remove(layerPropertyGroup.Path);
else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup)) else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup))
_expandedPropertyGroups.Add(layerPropertyGroup.Path); ExpandedPropertyGroups.Add(layerPropertyGroup.Path);
} }
#endregion #endregion
@ -172,12 +191,12 @@ namespace Artemis.Core
#region Effect management #region Effect management
protected List<BaseLayerEffect> _layerEffects; internal List<BaseLayerEffect> LayerEffectsList;
/// <summary> /// <summary>
/// Gets a read-only collection of the layer effects on this entity /// Gets a read-only collection of the layer effects on this entity
/// </summary> /// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly(); public ReadOnlyCollection<BaseLayerEffect> LayerEffects => LayerEffectsList.AsReadOnly();
/// <summary> /// <summary>
/// Adds a the layer effect described inthe provided <paramref name="descriptor" /> /// Adds a the layer effect described inthe provided <paramref name="descriptor" />
@ -208,7 +227,7 @@ namespace Artemis.Core
if (effect == null) throw new ArgumentNullException(nameof(effect)); if (effect == null) throw new ArgumentNullException(nameof(effect));
// Remove the effect from the layer and dispose it // Remove the effect from the layer and dispose it
_layerEffects.Remove(effect); LayerEffectsList.Remove(effect);
effect.Dispose(); effect.Dispose();
// Update the order on the remaining effects // Update the order on the remaining effects
@ -225,7 +244,7 @@ namespace Artemis.Core
index++; index++;
} }
_layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order)); LayerEffectsList.Sort((a, b) => a.Order.CompareTo(b.Order));
} }
internal void ActivateEffects() internal void ActivateEffects()
@ -233,7 +252,7 @@ namespace Artemis.Core
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects) foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects)
{ {
// If there is a non-placeholder existing effect, skip this entity // If there is a non-placeholder existing effect, skip this entity
BaseLayerEffect? existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id);
if (existing != null && existing.Descriptor.PlaceholderFor == null) if (existing != null && existing.Descriptor.PlaceholderFor == null)
continue; continue;
@ -243,7 +262,7 @@ namespace Artemis.Core
// If a descriptor is found but there is an existing placeholder, remove the placeholder // If a descriptor is found but there is an existing placeholder, remove the placeholder
if (existing != null) if (existing != null)
{ {
_layerEffects.Remove(existing); LayerEffectsList.Remove(existing);
existing.Dispose(); existing.Dispose();
} }
@ -264,19 +283,19 @@ namespace Artemis.Core
internal void ActivateLayerEffect(BaseLayerEffect layerEffect) internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
{ {
_layerEffects.Add(layerEffect); LayerEffectsList.Add(layerEffect);
OnLayerEffectsUpdated(); OnLayerEffectsUpdated();
} }
private void LayerEffectStoreOnLayerEffectRemoved(object sender, LayerEffectStoreEvent e) private void LayerEffectStoreOnLayerEffectRemoved(object sender, LayerEffectStoreEvent e)
{ {
// If effects provided by the plugin are on the element, replace them with placeholders // If effects provided by the plugin are on the element, replace them with placeholders
List<BaseLayerEffect> pluginEffects = _layerEffects.Where(ef => ef.Descriptor.Provider != null && List<BaseLayerEffect> pluginEffects = LayerEffectsList.Where(ef => ef.Descriptor.Provider != null &&
ef.ProviderId == e.Registration.PluginFeature.Id).ToList(); ef.ProviderId == e.Registration.PluginFeature.Id).ToList();
foreach (BaseLayerEffect pluginEffect in pluginEffects) foreach (BaseLayerEffect pluginEffect in pluginEffects)
{ {
LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId); LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId);
_layerEffects.Remove(pluginEffect); LayerEffectsList.Remove(pluginEffect);
pluginEffect.Dispose(); pluginEffect.Dispose();
LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId); LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId);
@ -344,7 +363,9 @@ namespace Artemis.Core
{ {
// Event conditions reset if the timeline finished // Event conditions reset if the timeline finished
if (Timeline.IsFinished) if (Timeline.IsFinished)
{
Timeline.JumpToStart(); Timeline.JumpToStart();
}
// and otherwise apply their overlap mode // and otherwise apply their overlap mode
else else
{ {
@ -365,7 +386,10 @@ namespace Artemis.Core
#region Events #region Events
public event EventHandler LayerEffectsUpdated; /// <summary>
/// Occurs when a layer effect has been added or removed to this render element
/// </summary>
public event EventHandler? LayerEffectsUpdated;
internal void OnLayerEffectsUpdated() internal void OnLayerEffectsUpdated()
{ {

View File

@ -35,7 +35,7 @@ namespace Artemis.Core
Bitmap = new SKBitmap(width, height); Bitmap = new SKBitmap(width, height);
Path = new SKPath(path); Path = new SKPath(path);
Canvas = new SKCanvas(Bitmap); Canvas = new SKCanvas(Bitmap);
Path.Transform(SKMatrix.MakeTranslation(pathBounds.Left * -1, pathBounds.Top * -1)); Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1));
TargetLocation = new SKPoint(pathBounds.Location.X, pathBounds.Location.Y); TargetLocation = new SKPoint(pathBounds.Location.X, pathBounds.Location.Y);
if (parent != null) if (parent != null)

View File

@ -429,6 +429,7 @@ namespace Artemis.Core
#endregion #endregion
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"Progress: {Position}/{Length} - delta: {Delta}"; return $"Progress: {Position}/{Length} - delta: {Delta}";

View File

@ -1,22 +1,26 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.DeviceProviders;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents an RGB device usable by Artemis, provided by a <see cref="DeviceProviders.DeviceProvider" />
/// </summary>
public class ArtemisDevice : CorePropertyChanged public class ArtemisDevice : CorePropertyChanged
{ {
private ReadOnlyCollection<ArtemisLed> _leds; private ReadOnlyCollection<ArtemisLed> _leds;
private SKPath _renderPath; private SKPath _renderPath;
private SKRect _renderRectangle; private SKRect _renderRectangle;
internal ArtemisDevice(IRGBDevice rgbDevice, PluginFeature pluginFeature, ArtemisSurface surface) internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface)
{ {
RgbDevice = rgbDevice; RgbDevice = rgbDevice;
PluginFeature = pluginFeature; DeviceProvider = deviceProvider;
Surface = surface; Surface = surface;
DeviceEntity = new DeviceEntity(); DeviceEntity = new DeviceEntity();
Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
@ -29,38 +33,60 @@ namespace Artemis.Core
CalculateRenderProperties(); CalculateRenderProperties();
} }
internal ArtemisDevice(IRGBDevice rgbDevice, PluginFeature pluginFeature, ArtemisSurface surface, DeviceEntity deviceEntity) internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface, DeviceEntity deviceEntity)
{ {
RgbDevice = rgbDevice; RgbDevice = rgbDevice;
PluginFeature = pluginFeature; DeviceProvider = deviceProvider;
Surface = surface; Surface = surface;
DeviceEntity = deviceEntity; DeviceEntity = deviceEntity;
Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
} }
/// <summary>
/// Gets the rectangle covering the device, sized to match the render scale
/// </summary>
public SKRect RenderRectangle public SKRect RenderRectangle
{ {
get => _renderRectangle; get => _renderRectangle;
private set => SetAndNotify(ref _renderRectangle, value); private set => SetAndNotify(ref _renderRectangle, value);
} }
/// <summary>
/// Gets the path surrounding the device, sized to match the render scale
/// </summary>
public SKPath RenderPath public SKPath RenderPath
{ {
get => _renderPath; get => _renderPath;
private set => SetAndNotify(ref _renderPath, value); private set => SetAndNotify(ref _renderPath, value);
} }
/// <summary>
/// Gets the RGB.NET device backing this Artemis device
/// </summary>
public IRGBDevice RgbDevice { get; } public IRGBDevice RgbDevice { get; }
public PluginFeature PluginFeature { get; }
public ArtemisSurface Surface { get; }
public DeviceEntity DeviceEntity { get; }
/// <summary>
/// Gets the device provider that provided this device
/// </summary>
public DeviceProvider DeviceProvider { get; }
/// <summary>
/// Gets the surface containing this device
/// </summary>
public ArtemisSurface Surface { get; }
/// <summary>
/// Gets a read only collection containing the LEDs of this device
/// </summary>
public ReadOnlyCollection<ArtemisLed> Leds public ReadOnlyCollection<ArtemisLed> Leds
{ {
get => _leds; get => _leds;
set => SetAndNotify(ref _leds, value); private set => SetAndNotify(ref _leds, value);
} }
/// <summary>
/// Gets or sets the X-position of the device
/// </summary>
public double X public double X
{ {
get => DeviceEntity.X; get => DeviceEntity.X;
@ -71,6 +97,9 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets or sets the Y-position of the device
/// </summary>
public double Y public double Y
{ {
get => DeviceEntity.Y; get => DeviceEntity.Y;
@ -81,6 +110,9 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets or sets the rotation of the device
/// </summary>
public double Rotation public double Rotation
{ {
get => DeviceEntity.Rotation; get => DeviceEntity.Rotation;
@ -91,6 +123,9 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets or sets the scale of the device
/// </summary>
public double Scale public double Scale
{ {
get => DeviceEntity.Scale; get => DeviceEntity.Scale;
@ -101,6 +136,9 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets or sets the Z-index of the device
/// </summary>
public int ZIndex public int ZIndex
{ {
get => DeviceEntity.ZIndex; get => DeviceEntity.ZIndex;
@ -111,13 +149,22 @@ namespace Artemis.Core
} }
} }
internal DeviceEntity DeviceEntity { get; }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"[{RgbDevice.DeviceInfo.DeviceType}] {RgbDevice.DeviceInfo.DeviceName} - {X}.{Y}.{ZIndex}"; return $"[{RgbDevice.DeviceInfo.DeviceType}] {RgbDevice.DeviceInfo.DeviceName} - {X}.{Y}.{ZIndex}";
} }
public event EventHandler DeviceUpdated; /// <summary>
/// Occurs when the underlying RGB.NET device was updated
/// </summary>
public event EventHandler? DeviceUpdated;
/// <summary>
/// Invokes the <see cref="DeviceUpdated" /> event
/// </summary>
protected virtual void OnDeviceUpdated() protected virtual void OnDeviceUpdated()
{ {
DeviceUpdated?.Invoke(this, EventArgs.Empty); DeviceUpdated?.Invoke(this, EventArgs.Empty);

View File

@ -3,35 +3,51 @@ using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents an RGB LED contained in an <see cref="ArtemisDevice" />
/// </summary>
public class ArtemisLed : CorePropertyChanged public class ArtemisLed : CorePropertyChanged
{ {
private SKRect _absoluteRenderRectangle; private SKRect _absoluteRenderRectangle;
private SKRect _renderRectangle; private SKRect _renderRectangle;
public ArtemisLed(Led led, ArtemisDevice device) internal ArtemisLed(Led led, ArtemisDevice device)
{ {
RgbLed = led; RgbLed = led;
Device = device; Device = device;
CalculateRenderRectangle(); CalculateRenderRectangle();
} }
public int LedIndex => Device.Leds.IndexOf(this); /// <summary>
/// Gets the RGB.NET LED backing this Artemis LED
/// </summary>
public Led RgbLed { get; } public Led RgbLed { get; }
/// <summary>
/// Gets the device that contains this LED
/// </summary>
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
/// <summary>
/// Gets the rectangle covering the LED, sized to match the render scale and positioned relative to the
/// <see cref="Device" />
/// </summary>
public SKRect RenderRectangle public SKRect RenderRectangle
{ {
get => _renderRectangle; get => _renderRectangle;
private set => SetAndNotify(ref _renderRectangle, value); private set => SetAndNotify(ref _renderRectangle, value);
} }
/// <summary>
/// Gets the rectangle covering the LED, sized to match the render scale
/// </summary>
public SKRect AbsoluteRenderRectangle public SKRect AbsoluteRenderRectangle
{ {
get => _absoluteRenderRectangle; get => _absoluteRenderRectangle;
private set => SetAndNotify(ref _absoluteRenderRectangle, value); private set => SetAndNotify(ref _absoluteRenderRectangle, value);
} }
public void CalculateRenderRectangle() internal void CalculateRenderRectangle()
{ {
RenderRectangle = SKRect.Create( RenderRectangle = SKRect.Create(
(RgbLed.LedRectangle.Location.X * Device.Surface.Scale).RoundToInt(), (RgbLed.LedRectangle.Location.X * Device.Surface.Scale).RoundToInt(),

View File

@ -6,6 +6,9 @@ using RGB.NET.Core;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a surface of a specific scale, containing all the available <see cref="ArtemisDevice" />s
/// </summary>
public class ArtemisSurface : CorePropertyChanged public class ArtemisSurface : CorePropertyChanged
{ {
private List<ArtemisDevice> _devices; private List<ArtemisDevice> _devices;
@ -17,14 +20,14 @@ namespace Artemis.Core
{ {
SurfaceEntity = new SurfaceEntity {DeviceEntities = new List<DeviceEntity>()}; SurfaceEntity = new SurfaceEntity {DeviceEntities = new List<DeviceEntity>()};
EntityId = Guid.NewGuid(); EntityId = Guid.NewGuid();
Name = name;
Scale = scale;
RgbSurface = rgbSurface; RgbSurface = rgbSurface;
IsActive = false;
_name = name;
_scale = scale;
_isActive = false;
// Devices are not populated here but as they are detected // Devices are not populated here but as they are detected
Devices = new List<ArtemisDevice>(); _devices = new List<ArtemisDevice>();
ApplyToEntity(); ApplyToEntity();
} }
@ -33,36 +36,51 @@ namespace Artemis.Core
{ {
SurfaceEntity = surfaceEntity; SurfaceEntity = surfaceEntity;
EntityId = surfaceEntity.Id; EntityId = surfaceEntity.Id;
RgbSurface = rgbSurface; RgbSurface = rgbSurface;
Scale = scale;
Name = surfaceEntity.Name; _scale = scale;
IsActive = surfaceEntity.IsActive; _name = surfaceEntity.Name;
_isActive = surfaceEntity.IsActive;
// Devices are not populated here but as they are detected // Devices are not populated here but as they are detected
Devices = new List<ArtemisDevice>(); _devices = new List<ArtemisDevice>();
} }
/// <summary>
/// Gets the RGB.NET surface backing this Artemis surface
/// </summary>
public RGBSurface RgbSurface { get; } public RGBSurface RgbSurface { get; }
/// <summary>
/// Gets the scale at which this surface is rendered
/// </summary>
public double Scale public double Scale
{ {
get => _scale; get => _scale;
private set => SetAndNotify(ref _scale, value); private set => SetAndNotify(ref _scale, value);
} }
/// <summary>
/// Gets the name of the surface
/// </summary>
public string Name public string Name
{ {
get => _name; get => _name;
set => SetAndNotify(ref _name, value); set => SetAndNotify(ref _name, value);
} }
/// <summary>
/// Gets a boolean indicating whether this surface is the currently active surface
/// </summary>
public bool IsActive public bool IsActive
{ {
get => _isActive; get => _isActive;
internal set => SetAndNotify(ref _isActive, value); internal set => SetAndNotify(ref _isActive, value);
} }
/// <summary>
/// Gets a list of devices this surface contains
/// </summary>
public List<ArtemisDevice> Devices public List<ArtemisDevice> Devices
{ {
get => _devices; get => _devices;
@ -72,6 +90,10 @@ namespace Artemis.Core
internal SurfaceEntity SurfaceEntity { get; set; } internal SurfaceEntity SurfaceEntity { get; set; }
internal Guid EntityId { get; set; } internal Guid EntityId { get; set; }
/// <summary>
/// Updates the scale of the surface
/// </summary>
/// <param name="value"></param>
public void UpdateScale(double value) public void UpdateScale(double value)
{ {
Scale = value; Scale = value;
@ -89,16 +111,20 @@ namespace Artemis.Core
// Add missing device entities, don't remove old ones in case they come back later // Add missing device entities, don't remove old ones in case they come back later
foreach (DeviceEntity deviceEntity in Devices.Select(d => d.DeviceEntity).ToList()) foreach (DeviceEntity deviceEntity in Devices.Select(d => d.DeviceEntity).ToList())
{
if (!SurfaceEntity.DeviceEntities.Contains(deviceEntity)) if (!SurfaceEntity.DeviceEntities.Contains(deviceEntity))
SurfaceEntity.DeviceEntities.Add(deviceEntity); SurfaceEntity.DeviceEntities.Add(deviceEntity);
} }
}
#region Events #region Events
public event EventHandler<EventArgs> ScaleChanged; /// <summary>
/// Occurs when the scale of the surface is changed
/// </summary>
public event EventHandler<EventArgs>? ScaleChanged;
/// <summary>
/// Invokes the <see cref="ScaleChanged" /> event
/// </summary>
protected virtual void OnScaleChanged() protected virtual void OnScaleChanged()
{ {
ScaleChanged?.Invoke(this, EventArgs.Empty); ScaleChanged?.Invoke(this, EventArgs.Empty);

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
@ -9,6 +8,9 @@ using Humanizer;
namespace Artemis.Core.DataModelExpansions namespace Artemis.Core.DataModelExpansions
{ {
/// <summary>
/// Represents a data model that contains information on a game/application etc.
/// </summary>
public abstract class DataModel public abstract class DataModel
{ {
private readonly Dictionary<string, DataModel> _dynamicDataModels = new Dictionary<string, DataModel>(); private readonly Dictionary<string, DataModel> _dynamicDataModels = new Dictionary<string, DataModel>();
@ -154,11 +156,17 @@ namespace Artemis.Core.DataModelExpansions
/// </summary> /// </summary>
public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelRemoved; public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelRemoved;
/// <summary>
/// Invokes the <see cref="DynamicDataModelAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e) protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e)
{ {
DynamicDataModelAdded?.Invoke(this, e); DynamicDataModelAdded?.Invoke(this, e);
} }
/// <summary>
/// Invokes the <see cref="DynamicDataModelRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e) protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e)
{ {
DynamicDataModelRemoved?.Invoke(this, e); DynamicDataModelRemoved?.Invoke(this, e);

View File

@ -1,7 +1,9 @@
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary>
/// Represents a configuration dialog for a <see cref="Plugin" />
/// </summary>
public interface IPluginConfigurationDialog public interface IPluginConfigurationDialog
{ {
} }
} }

View File

@ -9,8 +9,8 @@ namespace Artemis.Core.LayerBrushes
public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
{ {
private LayerBrushType _brushType; private LayerBrushType _brushType;
private ILayerBrushConfigurationDialog _configurationDialog; private ILayerBrushConfigurationDialog? _configurationDialog;
private LayerBrushDescriptor _descriptor; private LayerBrushDescriptor? _descriptor;
private Layer _layer; private Layer _layer;
private bool _supportsTransformation = true; private bool _supportsTransformation = true;
@ -26,7 +26,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary> /// <summary>
/// Gets the descriptor of this brush /// Gets the descriptor of this brush
/// </summary> /// </summary>
public LayerBrushDescriptor Descriptor public LayerBrushDescriptor? Descriptor
{ {
get => _descriptor; get => _descriptor;
internal set => SetAndNotify(ref _descriptor, value); internal set => SetAndNotify(ref _descriptor, value);
@ -35,7 +35,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary> /// <summary>
/// Gets or sets a configuration dialog complementing the regular properties /// Gets or sets a configuration dialog complementing the regular properties
/// </summary> /// </summary>
public ILayerBrushConfigurationDialog ConfigurationDialog public ILayerBrushConfigurationDialog? ConfigurationDialog
{ {
get => _configurationDialog; get => _configurationDialog;
protected set => SetAndNotify(ref _configurationDialog, value); protected set => SetAndNotify(ref _configurationDialog, value);
@ -53,12 +53,12 @@ namespace Artemis.Core.LayerBrushes
/// <summary> /// <summary>
/// Gets the ID of the <see cref="LayerBrushProvider" /> that provided this effect /// Gets the ID of the <see cref="LayerBrushProvider" /> that provided this effect
/// </summary> /// </summary>
public string ProviderId => Descriptor?.Provider?.Id; public string? ProviderId => Descriptor?.Provider.Id;
/// <summary> /// <summary>
/// Gets a reference to the layer property group without knowing it's type /// Gets a reference to the layer property group without knowing it's type
/// </summary> /// </summary>
public virtual LayerPropertyGroup BaseProperties => null; public virtual LayerPropertyGroup? BaseProperties => null;
/// <summary> /// <summary>
/// Gets or sets whether the brush supports transformations /// Gets or sets whether the brush supports transformations
@ -70,7 +70,7 @@ namespace Artemis.Core.LayerBrushes
protected set protected set
{ {
if (value && BrushType == LayerBrushType.RgbNet) if (value && BrushType == LayerBrushType.RgbNet)
throw new ArtemisPluginFeatureException(Descriptor.Provider, "An RGB.NET brush cannot support transformation"); throw new ArtemisPluginFeatureException(Descriptor?.Provider!, "An RGB.NET brush cannot support transformation");
_supportsTransformation = value; _supportsTransformation = value;
} }
} }
@ -105,7 +105,7 @@ namespace Artemis.Core.LayerBrushes
public void Dispose() public void Dispose()
{ {
DisableLayerBrush(); DisableLayerBrush();
BaseProperties.Dispose(); BaseProperties?.Dispose();
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@ -46,7 +46,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary> /// <summary>
/// Determines whether the provided <paramref name="reference" /> references to a brush provided by this descriptor /// Determines whether the provided <paramref name="reference" /> references to a brush provided by this descriptor
/// </summary> /// </summary>
public bool MatchesLayerBrushReference(LayerBrushReference reference) public bool MatchesLayerBrushReference(LayerBrushReference? reference)
{ {
if (reference == null) if (reference == null)
return false; return false;

View File

@ -1,6 +0,0 @@
namespace Artemis.Core.LayerEffects
{
public interface IEffectConfigurationViewModel
{
}
}

View File

@ -1,5 +1,8 @@
namespace Artemis.Core.LayerEffects namespace Artemis.Core.LayerEffects
{ {
/// <summary>
/// Represents a configuration dialog for a <see cref="LayerEffect{T}" />
/// </summary>
public interface ILayerEffectConfigurationDialog public interface ILayerEffectConfigurationDialog
{ {
} }

View File

@ -160,6 +160,7 @@ namespace Artemis.Core
} }
} }
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
foreach (PluginFeature feature in Features) foreach (PluginFeature feature in Features)

View File

@ -104,7 +104,7 @@ namespace Artemis.Core
} }
} }
private void TimerOnElapsed(object sender, ElapsedEventArgs e) private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
{ {
if (!Feature.IsEnabled) if (!Feature.IsEnabled)
return; return;
@ -128,12 +128,12 @@ namespace Artemis.Core
} }
} }
private void FeatureOnEnabled(object sender, EventArgs e) private void FeatureOnEnabled(object? sender, EventArgs e)
{ {
Start(); Start();
} }
private void FeatureOnDisabled(object sender, EventArgs e) private void FeatureOnDisabled(object? sender, EventArgs e)
{ {
Stop(); Stop();
} }

View File

@ -198,7 +198,7 @@ namespace Artemis.Core.Services
module.InternalRender(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info); module.InternalRender(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info);
} }
OnFrameRendering(new FrameRenderingEventArgs(modules, canvas, args.DeltaTime, _rgbService.Surface)); OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
} }
} }
catch (Exception e) catch (Exception e)

View File

@ -44,6 +44,10 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
/// <param name="plugin">The plugin to enable</param> /// <param name="plugin">The plugin to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param> /// <param name="saveState">Whether or not to save the new enabled state</param>
/// <param name="ignorePluginLock">
/// Whether or not plugin lock files should be ignored. If set to <see langword="true" />,
/// plugins with lock files will load successfully
/// </param>
void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false); void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false);
/// <summary> /// <summary>
@ -95,7 +99,7 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
/// <param name="assembly"></param> /// <param name="assembly"></param>
/// <returns></returns> /// <returns></returns>
Plugin GetPluginByAssembly(Assembly assembly); Plugin? GetPluginByAssembly(Assembly? assembly);
/// <summary> /// <summary>
/// Returns the plugin info of the current call stack /// Returns the plugin info of the current call stack

View File

@ -138,8 +138,10 @@ namespace Artemis.Core.Services
} }
} }
public Plugin? GetPluginByAssembly(Assembly assembly) public Plugin? GetPluginByAssembly(Assembly? assembly)
{ {
if (assembly == null)
return null;
lock (_plugins) lock (_plugins)
{ {
return _plugins.FirstOrDefault(p => p.Assembly == assembly); return _plugins.FirstOrDefault(p => p.Assembly == assembly);
@ -159,8 +161,8 @@ namespace Artemis.Core.Services
foreach (StackFrame stackFrame in stackFrames) foreach (StackFrame stackFrame in stackFrames)
{ {
Assembly assembly = stackFrame.GetMethod().DeclaringType.Assembly; Assembly? assembly = stackFrame.GetMethod()?.DeclaringType?.Assembly;
Plugin plugin = GetPluginByAssembly(assembly); Plugin? plugin = GetPluginByAssembly(assembly);
if (plugin != null) if (plugin != null)
return plugin; return plugin;
} }
@ -207,8 +209,10 @@ namespace Artemis.Core.Services
public void UnloadPlugins() public void UnloadPlugins()
{ {
// Unload all plugins // Unload all plugins
// ReSharper disable InconsistentlySynchronizedField - UnloadPlugin will lock it when it has to
while (_plugins.Count > 0) while (_plugins.Count > 0)
UnloadPlugin(_plugins[0]); UnloadPlugin(_plugins[0]);
// ReSharper restore InconsistentlySynchronizedField
lock (_plugins) lock (_plugins)
{ {
@ -297,7 +301,11 @@ namespace Artemis.Core.Services
} }
catch (ReflectionTypeLoadException e) catch (ReflectionTypeLoadException e)
{ {
throw new ArtemisPluginException(plugin, "Failed to initialize the plugin assembly", new AggregateException(e.LoaderExceptions)); throw new ArtemisPluginException(
plugin,
"Failed to initialize the plugin assembly",
new AggregateException(e.LoaderExceptions.Where(le => le != null).Cast<Exception>().ToArray())
);
} }
if (!featureTypes.Any()) if (!featureTypes.Any())
@ -318,7 +326,7 @@ namespace Artemis.Core.Services
// Load the enabled state and if not found, default to true // Load the enabled state and if not found, default to true
instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ?? instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName}; new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName!};
} }
catch (Exception e) catch (Exception e)
{ {
@ -385,7 +393,7 @@ namespace Artemis.Core.Services
plugin.SetEnabled(false); plugin.SetEnabled(false);
plugin.Kernel.Dispose(); plugin.Kernel?.Dispose();
plugin.Kernel = null; plugin.Kernel = null;
GC.Collect(); GC.Collect();
@ -487,28 +495,22 @@ namespace Artemis.Core.Services
_pluginRepository.SavePlugin(plugin.Entity); _pluginRepository.SavePlugin(plugin.Entity);
} }
private PluginFeatureEntity GetOrCreateFeatureEntity(PluginFeature feature)
{
return feature.Plugin.Entity.Features.FirstOrDefault(i => i.Type == feature.GetType().FullName) ??
new PluginFeatureEntity {IsEnabled = true, Type = feature.GetType().FullName};
}
#endregion #endregion
#region Events #region Events
public event EventHandler CopyingBuildInPlugins; public event EventHandler? CopyingBuildInPlugins;
public event EventHandler<PluginEventArgs> PluginLoading; public event EventHandler<PluginEventArgs>? PluginLoading;
public event EventHandler<PluginEventArgs> PluginLoaded; public event EventHandler<PluginEventArgs>? PluginLoaded;
public event EventHandler<PluginEventArgs> PluginUnloaded; public event EventHandler<PluginEventArgs>? PluginUnloaded;
public event EventHandler<PluginEventArgs> PluginEnabling; public event EventHandler<PluginEventArgs>? PluginEnabling;
public event EventHandler<PluginEventArgs> PluginEnabled; public event EventHandler<PluginEventArgs>? PluginEnabled;
public event EventHandler<PluginEventArgs> PluginDisabled; public event EventHandler<PluginEventArgs>? PluginDisabled;
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabling; public event EventHandler<PluginFeatureEventArgs>? PluginFeatureEnabling;
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabled; public event EventHandler<PluginFeatureEventArgs>? PluginFeatureEnabled;
public event EventHandler<PluginFeatureEventArgs> PluginFeatureDisabled; public event EventHandler<PluginFeatureEventArgs>? PluginFeatureDisabled;
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnableFailed; public event EventHandler<PluginFeatureEventArgs>? PluginFeatureEnableFailed;
protected virtual void OnCopyingBuildInPlugins() protected virtual void OnCopyingBuildInPlugins()
{ {

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.DeviceProviders;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core; using RGB.NET.Core;
@ -44,8 +45,8 @@ namespace Artemis.Core.Services
// Add all current devices // Add all current devices
foreach (IRGBDevice rgbDevice in _rgbService.LoadedDevices) foreach (IRGBDevice rgbDevice in _rgbService.LoadedDevices)
{ {
PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice);
configuration.Devices.Add(new ArtemisDevice(rgbDevice, pluginFeature, configuration)); configuration.Devices.Add(new ArtemisDevice(rgbDevice, deviceProvider, configuration));
} }
lock (_surfaceConfigurations) lock (_surfaceConfigurations)
@ -136,8 +137,8 @@ namespace Artemis.Core.Services
IRGBDevice device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier); IRGBDevice device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier);
if (device != null) if (device != null)
{ {
PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(device); DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(device);
surfaceConfiguration.Devices.Add(new ArtemisDevice(device, pluginFeature, surfaceConfiguration, position)); surfaceConfiguration.Devices.Add(new ArtemisDevice(device, deviceProvider, surfaceConfiguration, position));
} }
} }
@ -178,8 +179,8 @@ namespace Artemis.Core.Services
DeviceEntity existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier); DeviceEntity existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier);
if (existingDeviceConfig != null) if (existingDeviceConfig != null)
{ {
PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice);
device = new ArtemisDevice(rgbDevice, pluginFeature, surface, existingDeviceConfig); device = new ArtemisDevice(rgbDevice, deviceProvider, surface, existingDeviceConfig);
} }
// Fall back on creating a new device // Fall back on creating a new device
else else
@ -189,8 +190,8 @@ namespace Artemis.Core.Services
rgbDevice.DeviceInfo, rgbDevice.DeviceInfo,
deviceIdentifier deviceIdentifier
); );
PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice);
device = new ArtemisDevice(rgbDevice, pluginFeature, surface); device = new ArtemisDevice(rgbDevice, deviceProvider, surface);
} }
surface.Devices.Add(device); surface.Devices.Add(device);

View File

@ -38,7 +38,7 @@ namespace Artemis.Core
OnConditionOperatorRemoved(new ConditionOperatorStoreEvent(registration)); OnConditionOperatorRemoved(new ConditionOperatorStoreEvent(registration));
} }
public static ConditionOperatorRegistration Get(Guid pluginGuid, string type) public static ConditionOperatorRegistration? Get(Guid pluginGuid, string type)
{ {
lock (Registrations) lock (Registrations)
{ {

View File

@ -10,6 +10,9 @@ namespace Artemis.Core
public static DataBindingModifierTypeRegistration Add(BaseDataBindingModifierType modifierType) 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; DataBindingModifierTypeRegistration typeRegistration;
lock (Registrations) lock (Registrations)
{ {
@ -38,7 +41,7 @@ namespace Artemis.Core
OnDataBindingModifierRemoved(new DataBindingModifierTypeStoreEvent(typeRegistration)); OnDataBindingModifierRemoved(new DataBindingModifierTypeStoreEvent(typeRegistration));
} }
public static DataBindingModifierTypeRegistration Get(Guid pluginGuid, string type) public static DataBindingModifierTypeRegistration? Get(Guid pluginGuid, string type)
{ {
lock (Registrations) lock (Registrations)
{ {
@ -74,8 +77,8 @@ namespace Artemis.Core
#region Events #region Events
public static event EventHandler<DataBindingModifierTypeStoreEvent> DataBindingModifierAdded; public static event EventHandler<DataBindingModifierTypeStoreEvent>? DataBindingModifierAdded;
public static event EventHandler<DataBindingModifierTypeStoreEvent> DataBindingModifierRemoved; public static event EventHandler<DataBindingModifierTypeStoreEvent>? DataBindingModifierRemoved;
private static void OnDataBindingModifierAdded(DataBindingModifierTypeStoreEvent e) private static void OnDataBindingModifierAdded(DataBindingModifierTypeStoreEvent e)
{ {

View File

@ -47,7 +47,7 @@ namespace Artemis.Core
} }
} }
public static DataModelRegistration Get(string id) public static DataModelRegistration? Get(string id)
{ {
lock (Registrations) lock (Registrations)
{ {

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Windows;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -67,7 +66,10 @@ namespace Artemis.Core
#region Events #region Events
public static event EventHandler ShutdownRequested; /// <summary>
/// Occurs when the core has requested an application shutdown
/// </summary>
public static event EventHandler? ShutdownRequested;
private static void OnShutdownRequested() private static void OnShutdownRequested()
{ {
@ -75,7 +77,5 @@ namespace Artemis.Core
} }
#endregion #endregion
} }
} }

View File

@ -28,6 +28,7 @@
<NrtRequiredVcs>git</NrtRequiredVcs> <NrtRequiredVcs>git</NrtRequiredVcs>
<NrtShowRevision>true</NrtShowRevision> <NrtShowRevision>true</NrtShowRevision>
<Version>2.0.0</Version> <Version>2.0.0</Version>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DocumentationFile>bin\x64\Release\Artemis.UI.Shared.xml</DocumentationFile> <DocumentationFile>bin\x64\Release\Artemis.UI.Shared.xml</DocumentationFile>

Some files were not shown because too many files have changed in this diff Show More